From 5f3f6988bc18358293e53b59799faf05ff11644a Mon Sep 17 00:00:00 2001 From: Felix Kunde Date: Thu, 17 Dec 2020 12:07:40 +0100 Subject: [PATCH 01/35] add logical-backup build and push to delivery.yaml (#1259) * add logical-backup build and push to delivery.yaml * enable manual approval for UI and logical-backup --- delivery.yaml | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/delivery.yaml b/delivery.yaml index a4d42af7d..fca5af7ea 100644 --- a/delivery.yaml +++ b/delivery.yaml @@ -55,7 +55,7 @@ pipeline: - id: build-operator-ui type: script - + requires_human_approval: true commands: - desc: 'Prepare environment' cmd: | @@ -80,3 +80,15 @@ pipeline: export IMAGE make docker make push + + - id: build-logical-backup + type: script + requires_human_approval: true + commands: + - desc: Build image + cmd: | + cd docker/logical-backup + export TAG=$(git describe --tags --always --dirty) + IMAGE="registry-write.opensource.zalan.do/acid/logical-backup" + docker build --rm -t "$IMAGE:$TAG$CDP_TAG" . + docker push "$IMAGE:$TAG$CDP_TAG" From a63ad49ef8101bb58725d892151279cd751df5c0 Mon Sep 17 00:00:00 2001 From: Jan Mussler Date: Thu, 17 Dec 2020 15:00:29 +0100 Subject: [PATCH 02/35] Initial commit for new 1.6 release with Postgres 13 support. (#1257) * Initial commit for new 1.6 release with Postgres 13 support. * Updating maintainers, Go version, Codeowners. * Use lazy upgrade image that contains pg13. * fix typo for ownerReference * fix clusterrole in helm chart * reflect GCP logical backup in validation * improve PostgresTeam docs * change defaults for enable_pgversion_env_var and storage_resize_mode * explain manual part of in-place upgrade * remove gsoc docs Co-authored-by: Felix Kunde --- .github/workflows/run_e2e.yaml | 2 +- .github/workflows/run_tests.yaml | 2 +- CODEOWNERS | 2 +- MAINTAINERS | 4 +- README.md | 26 ++- .../crds/operatorconfigurations.yaml | 4 + .../templates/clusterrole.yaml | 40 +++-- charts/postgres-operator/values-crd.yaml | 2 +- charts/postgres-operator/values.yaml | 2 +- delivery.yaml | 2 +- docs/administrator.md | 28 +++- docs/developer.md | 2 +- docs/gsoc-2019/ideas.md | 63 -------- docs/reference/operator_parameters.md | 4 +- docs/user.md | 148 +++++++++++++----- e2e/run.sh | 2 +- e2e/tests/test_e2e.py | 4 +- manifests/complete-postgres-manifest.yaml | 8 +- manifests/configmap.yaml | 10 +- manifests/minimal-postgres-manifest.yaml | 2 +- manifests/operatorconfiguration.crd.yaml | 4 + ...gresql-operator-default-configuration.yaml | 2 +- mkdocs.yml | 1 - pkg/apis/acid.zalan.do/v1/crds.go | 6 + pkg/cluster/connection_pooler_test.go | 4 +- pkg/cluster/k8sres_test.go | 4 +- pkg/controller/operator_config.go | 2 +- pkg/util/config/config.go | 4 +- ui/manifests/deployment.yaml | 6 +- ui/operator_ui/main.py | 2 +- 30 files changed, 233 insertions(+), 159 deletions(-) delete mode 100644 docs/gsoc-2019/ideas.md diff --git a/.github/workflows/run_e2e.yaml b/.github/workflows/run_e2e.yaml index 6eef5c8f2..cff0d49ef 100644 --- a/.github/workflows/run_e2e.yaml +++ b/.github/workflows/run_e2e.yaml @@ -14,7 +14,7 @@ jobs: - uses: actions/checkout@v1 - uses: actions/setup-go@v2 with: - go-version: "^1.15.5" + go-version: "^1.15.6" - name: Make dependencies run: make deps mocks - name: Compile diff --git a/.github/workflows/run_tests.yaml b/.github/workflows/run_tests.yaml index 56d147990..ebcdfedf6 100644 --- a/.github/workflows/run_tests.yaml +++ b/.github/workflows/run_tests.yaml @@ -14,7 +14,7 @@ jobs: - uses: actions/checkout@v1 - uses: actions/setup-go@v2 with: - go-version: "^1.15.5" + go-version: "^1.15.6" - name: Make dependencies run: make deps mocks - name: Compile diff --git a/CODEOWNERS b/CODEOWNERS index 96fe74510..398856c66 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1,2 +1,2 @@ # global owners -* @alexeyklyukin @erthalion @sdudoladov @Jan-M @CyberDem0n @avaczi @FxKu @RafiaSabih +* @erthalion @sdudoladov @Jan-M @CyberDem0n @avaczi @FxKu @RafiaSabih diff --git a/MAINTAINERS b/MAINTAINERS index 4f4ca87ba..572e6d971 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1,3 +1,5 @@ -Oleksii Kliukin Dmitrii Dolgov Sergey Dudoladov +Felix Kunde +Jan Mussler +Rafia Sabih \ No newline at end of file diff --git a/README.md b/README.md index 5e00dba8b..465e726d4 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ pipelines with no access to Kubernetes API directly, promoting infrastructure as ### Operator features * Rolling updates on Postgres cluster changes, incl. quick minor version updates -* Live volume resize without pod restarts (AWS EBS, others pending) +* Live volume resize without pod restarts (AWS EBS, PvC) * Database connection pooler with PGBouncer * Restore and cloning Postgres clusters (incl. major version upgrade) * Additionally logical backups to S3 bucket can be configured @@ -23,10 +23,12 @@ pipelines with no access to Kubernetes API directly, promoting infrastructure as * Basic credential and user management on K8s, eases application deployments * UI to create and edit Postgres cluster manifests * Works well on Amazon AWS, Google Cloud, OpenShift and locally on Kind +* Support for custom TLS certificates +* Base support for AWS EBS gp3 migration (iops, throughput pending) ### PostgreSQL features -* Supports PostgreSQL 12, starting from 9.6+ +* Supports PostgreSQL 13, starting from 9.6+ * Streaming replication cluster via Patroni * Point-In-Time-Recovery with [pg_basebackup](https://www.postgresql.org/docs/11/app-pgbasebackup.html) / @@ -48,7 +50,25 @@ pipelines with no access to Kubernetes API directly, promoting infrastructure as [timescaledb](https://github.com/timescale/timescaledb) The Postgres Operator has been developed at Zalando and is being used in -production for over two years. +production for over three years. + +## Notes on Postgres 13 support + +If you are new to the operator, you can skip this and just start using the Postgres operator as is, Postgres 13 is ready to go. + +The Postgres operator supports Postgres 13 with the new Spilo Image that includes also the recent Patroni version to support PG13 settings. +More work on optimizing restarts and rolling upgrades is pending. + +If you are already using the Postgres operator in older version with a Spilo 12 Docker Image you need to be aware of the changes for the backup path. +We introduce the major version into the backup path to smooth the major version upgrade that is now supported manually. + +The new operator configuration, sets a compatilibty flag *enable_spilo_wal_path_compat* to make Spilo look in current path but also old format paths for wal segments. +This comes at potential perf. costs, and should be disabled after a few days. + +The new Spilo 13 image is: `registry.opensource.zalan.do/acid/spilo-13:2.0-p1` + +The last Spilo 12 image is: `registry.opensource.zalan.do/acid/spilo-12:1.6-p5` + ## Getting started diff --git a/charts/postgres-operator/crds/operatorconfigurations.yaml b/charts/postgres-operator/crds/operatorconfigurations.yaml index 2cbc2cfce..2ac9ca7fc 100644 --- a/charts/postgres-operator/crds/operatorconfigurations.yaml +++ b/charts/postgres-operator/crds/operatorconfigurations.yaml @@ -319,6 +319,10 @@ spec: properties: logical_backup_docker_image: type: string + logical_backup_google_application_credentials: + type: string + logical_backup_provider: + type: string logical_backup_s3_access_key_id: type: string logical_backup_s3_bucket: diff --git a/charts/postgres-operator/templates/clusterrole.yaml b/charts/postgres-operator/templates/clusterrole.yaml index 46113c4f1..165cce7c6 100644 --- a/charts/postgres-operator/templates/clusterrole.yaml +++ b/charts/postgres-operator/templates/clusterrole.yaml @@ -44,13 +44,6 @@ rules: - get - patch - update -# to read configuration from ConfigMaps -- apiGroups: - - "" - resources: - - configmaps - verbs: - - get # to send events to the CRs - apiGroups: - "" @@ -64,14 +57,11 @@ rules: - update - watch # to manage endpoints/configmaps which are also used by Patroni +{{- if toString .Values.configGeneral.kubernetes_use_configmaps | eq "true" }} - apiGroups: - "" resources: -{{- if toString .Values.configGeneral.kubernetes_use_configmaps | eq "true" }} - configmaps -{{- else }} - - endpoints -{{- end }} verbs: - create - delete @@ -81,6 +71,34 @@ rules: - patch - update - watch +- apiGroups: + - "" + resources: + - endpoints + verbs: + - get +{{- else }} +# to read configuration from ConfigMaps +- apiGroups: + - "" + resources: + - configmaps + verbs: + - get +- apiGroups: + - "" + resources: + - endpoints + verbs: + - create + - delete + - deletecollection + - get + - list + - patch + - update + - watch +{{- end }} # to CRUD secrets for database access - apiGroups: - "" diff --git a/charts/postgres-operator/values-crd.yaml b/charts/postgres-operator/values-crd.yaml index fc8e36ec9..b52f021c8 100644 --- a/charts/postgres-operator/values-crd.yaml +++ b/charts/postgres-operator/values-crd.yaml @@ -22,7 +22,7 @@ configGeneral: # update only the statefulsets without immediately doing the rolling update enable_lazy_spilo_upgrade: false # set the PGVERSION env var instead of providing the version via postgresql.bin_dir in SPILO_CONFIGURATION - enable_pgversion_env_var: false + enable_pgversion_env_var: true # start any new database pod without limitations on shm memory enable_shm_volume: true # enables backwards compatible path between Spilo 12 and Spilo 13 images diff --git a/charts/postgres-operator/values.yaml b/charts/postgres-operator/values.yaml index 65340d1cd..e17d78840 100644 --- a/charts/postgres-operator/values.yaml +++ b/charts/postgres-operator/values.yaml @@ -25,7 +25,7 @@ configGeneral: # update only the statefulsets without immediately doing the rolling update enable_lazy_spilo_upgrade: "false" # set the PGVERSION env var instead of providing the version via postgresql.bin_dir in SPILO_CONFIGURATION - enable_pgversion_env_var: "false" + enable_pgversion_env_var: "true" # start any new database pod without limitations on shm memory enable_shm_volume: "true" # enables backwards compatible path between Spilo 12 and Spilo 13 images diff --git a/delivery.yaml b/delivery.yaml index fca5af7ea..d8b8d724a 100644 --- a/delivery.yaml +++ b/delivery.yaml @@ -16,7 +16,7 @@ pipeline: - desc: 'Install go' cmd: | cd /tmp - wget -q https://storage.googleapis.com/golang/go1.15.5.linux-amd64.tar.gz -O go.tar.gz + wget -q https://storage.googleapis.com/golang/go1.15.6.linux-amd64.tar.gz -O go.tar.gz tar -xf go.tar.gz mv go /usr/local ln -s /usr/local/go/bin/go /usr/bin/go diff --git a/docs/administrator.md b/docs/administrator.md index 64bb10b68..1e18c31f4 100644 --- a/docs/administrator.md +++ b/docs/administrator.md @@ -11,15 +11,29 @@ switchover (planned failover) of the master to the Pod with new minor version. The switch should usually take less than 5 seconds, still clients have to reconnect. -Major version upgrades are supported either via [cloning](user.md#how-to-clone-an-existing-postgresql-cluster)or in-place. +Major version upgrades are supported either via [cloning](user.md#how-to-clone-an-existing-postgresql-cluster) +or in-place. -With cloning, the new cluster manifest must have a higher `version` string than the source -cluster and will be created from a basebackup. Depending of the cluster size, -downtime in this case can be significant as writes to the database should be -stopped and all WAL files should be archived first before cloning is started. +With cloning, the new cluster manifest must have a higher `version` string than +the source cluster and will be created from a basebackup. Depending of the +cluster size, downtime in this case can be significant as writes to the database +should be stopped and all WAL files should be archived first before cloning is +started. -Starting with Spilo 13, Postgres Operator can do in-place major version upgrade, which should be faster than cloning. To trigger the upgrade, simply increase the version in the cluster manifest. As the very last step of -processing the manifest update event, the operator will call the `inplace_upgrade.py` script in Spilo. The upgrade is usually fast, well under one minute for most DBs. Note the changes become irrevertible once `pg_upgrade` is called. To understand the upgrade procedure, refer to the [corresponding PR in Spilo](https://github.com/zalando/spilo/pull/488). +Starting with Spilo 13, Postgres Operator can do in-place major version upgrade, +which should be faster than cloning. However, it is not fully automatic yet. +First, you need to make sure, that setting the PG_VERSION environment variable +is enabled in the configuration. Since `v1.6.0`, `enable_pgversion_env_var` is +enabled by default. + +To trigger the upgrade, increase the version in the cluster manifest. After +Pods are rotated `configure_spilo` will notice the version mismatch and start +the old version again. You can then exec into the Postgres container of the +master instance and call `python3 /scripts/inplace_upgrade.py N` where `N` +is the number of members of your cluster (see `number_of_instances`). The +upgrade is usually fast, well under one minute for most DBs. Note, that changes +become irrevertible once `pg_upgrade` is called. To understand the upgrade +procedure, refer to the [corresponding PR in Spilo](https://github.com/zalando/spilo/pull/488). ## CRD Validation diff --git a/docs/developer.md b/docs/developer.md index 59fbe09a2..3316ac4cc 100644 --- a/docs/developer.md +++ b/docs/developer.md @@ -286,7 +286,7 @@ manifest files: Postgres manifest parameters are defined in the [api package](../pkg/apis/acid.zalan.do/v1/postgresql_type.go). The operator behavior has to be implemented at least in [k8sres.go](../pkg/cluster/k8sres.go). -Validation of CRD parameters is controlled in [crd.go](../pkg/apis/acid.zalan.do/v1/crds.go). +Validation of CRD parameters is controlled in [crds.go](../pkg/apis/acid.zalan.do/v1/crds.go). Please, reflect your changes in tests, for example in: * [config_test.go](../pkg/util/config/config_test.go) * [k8sres_test.go](../pkg/cluster/k8sres_test.go) diff --git a/docs/gsoc-2019/ideas.md b/docs/gsoc-2019/ideas.md deleted file mode 100644 index 456a5a0ff..000000000 --- a/docs/gsoc-2019/ideas.md +++ /dev/null @@ -1,63 +0,0 @@ -

Google Summer of Code 2019

- -## Applications steps - -1. Please carefully read the official [Google Summer of Code Student Guide](https://google.github.io/gsocguides/student/) -2. Join the #postgres-operator slack channel under [Postgres Slack](https://postgres-slack.herokuapp.com) to introduce yourself to the community and get quick feedback on your application. -3. Select a project from the list of ideas below or propose your own. -4. Write a proposal draft. Please open an issue with the label `gsoc2019_application` in the [operator repository](https://github.com/zalando/postgres-operator/issues) so that the community members can publicly review it. See proposal instructions below for details. -5. Submit proposal and the proof of enrollment before April 9 2019 18:00 UTC through the web site of the Program. - -## Project ideas - - -### Place database pods into the "Guaranteed" Quality-of-Service class - -* **Description**: Kubernetes runtime does not kill pods in this class on condition they stay within their resource limits, which is desirable for the DB pods serving production workloads. To be assigned to that class, pod's resources must equal its limits. The task is to add the `enableGuaranteedQoSClass` or the like option to the Postgres manifest and the operator configmap that forcibly re-write pod resources to match the limits. -* **Recommended skills**: golang, basic Kubernetes abstractions -* **Difficulty**: moderate -* **Mentor(s)**: Felix Kunde [@FxKu](https://github.com/fxku), Sergey Dudoladov [@sdudoladov](https://github.com/sdudoladov) - -### Implement the kubectl plugin for the Postgres CustomResourceDefinition - -* **Description**: [kubectl plugins](https://kubernetes.io/docs/tasks/extend-kubectl/kubectl-plugins/) enable extending the Kubernetes command-line client `kubectl` with commands to manage custom resources. The task is to design and implement a plugin for the `kubectl postgres` command, -that can enable, for example, correct deletion or major version upgrade of Postgres clusters. -* **Recommended skills**: golang, shell scripting, operational experience with Kubernetes -* **Difficulty**: moderate to medium, depending on the plugin design -* **Mentor(s)**: Felix Kunde [@FxKu](https://github.com/fxku), Sergey Dudoladov [@sdudoladov](https://github.com/sdudoladov) - -### Implement the openAPIV3Schema for the Postgres CRD - -* **Description**: at present the operator validates a database manifest on its own. -It will be helpful to reject erroneous manifests before they reach the operator using the [native Kubernetes CRD validation](https://kubernetes.io/docs/tasks/access-kubernetes-api/custom-resources/custom-resource-definitions/#validation). It is up to the student to decide whether to write the schema manually or to adopt existing [schema generator developed for the Prometheus project](https://github.com/ant31/crd-validation). -* **Recommended skills**: golang, JSON schema -* **Difficulty**: medium -* **Mentor(s)**: Sergey Dudoladov [@sdudoladov](https://github.com/sdudoladov) -* **Issue**: [#388](https://github.com/zalando/postgres-operator/issues/388) - -### Design a solution for the local testing of the operator - -* **Description**: The current way of testing is to run minikube, either manually or with some tooling around it like `/run-operator_locally.sh` or Vagrant. This has at least three problems: -First, minikube is a single node cluster, so it is unsuitable for testing vital functions such as pod migration between nodes. Second, minikube starts slowly; that prolongs local testing. -Third, every contributor needs to come up with their own solution for local testing. The task is to come up with a better option which will enable us to conveniently and uniformly run e2e tests locally / potentially in Travis CI. -A promising option is the Kubernetes own [kind](https://github.com/kubernetes-sigs/kind) -* **Recommended skills**: Docker, shell scripting, basic Kubernetes abstractions -* **Difficulty**: medium to hard depending on the selected desing -* **Mentor(s)**: Dmitry Dolgov [@erthalion](https://github.com/erthalion), Sergey Dudoladov [@sdudoladov](https://github.com/sdudoladov) -* **Issue**: [#475](https://github.com/zalando/postgres-operator/issues/475) - -### Detach a Postgres cluster from the operator for maintenance - -* **Description**: sometimes a Postgres cluster requires manual maintenance. During such maintenance the operator should ignore all the changes manually applied to the cluster. - Currently the only way to achieve this behavior is to shutdown the operator altogether, for instance by scaling down the operator's own deployment to zero pods. That approach evidently affects all Postgres databases under the operator control and thus is highly undesirable in production Kubernetes clusters. It would be much better to be able to detach only the desired Postgres cluster from the operator for the time being and re-attach it again after maintenance. -* **Recommended skills**: golang, architecture of a Kubernetes operator -* **Difficulty**: hard - requires significant modification of the operator's internals and careful consideration of the corner cases. -* **Mentor(s)**: Dmitry Dolgov [@erthalion](https://github.com/erthalion), Sergey Dudoladov [@sdudoladov](https://github.com/sdudoladov) -* **Issue**: [#421](https://github.com/zalando/postgres-operator/issues/421) - -### Propose your own idea - -Feel free to come up with your own ideas. For inspiration, -see [our bug tracker](https://github.com/zalando/postgres-operator/issues), -the [official `CustomResouceDefinition` docs](https://kubernetes.io/docs/tasks/access-kubernetes-api/custom-resources/custom-resource-definitions/) -and [other operators](https://github.com/operator-framework/awesome-operators). diff --git a/docs/reference/operator_parameters.md b/docs/reference/operator_parameters.md index cd1cf8782..01e5c4039 100644 --- a/docs/reference/operator_parameters.md +++ b/docs/reference/operator_parameters.md @@ -80,7 +80,7 @@ Those are top-level keys, containing both leaf keys and groups. The default is `false`. * **enable_pgversion_env_var** - With newer versions of Spilo, it is preferable to use `PGVERSION` pod environment variable instead of the setting `postgresql.bin_dir` in the `SPILO_CONFIGURATION` env variable. When this option is true, the operator sets `PGVERSION` and omits `postgresql.bin_dir` from `SPILO_CONFIGURATION`. When false, the `postgresql.bin_dir` is set. This setting takes precedence over `PGVERSION`; see PR 222 in Spilo. The default is `false`. + With newer versions of Spilo, it is preferable to use `PGVERSION` pod environment variable instead of the setting `postgresql.bin_dir` in the `SPILO_CONFIGURATION` env variable. When this option is true, the operator sets `PGVERSION` and omits `postgresql.bin_dir` from `SPILO_CONFIGURATION`. When false, the `postgresql.bin_dir` is set. This setting takes precedence over `PGVERSION`; see PR 222 in Spilo. The default is `true`. * **enable_spilo_wal_path_compat** enables backwards compatible path between Spilo 12 and Spilo 13 images. The default is `false`. @@ -375,7 +375,7 @@ configuration they are grouped under the `kubernetes` key. * **storage_resize_mode** defines how operator handels the difference between requested volume size and actual size. Available options are: ebs - tries to resize EBS volume, pvc - - changes PVC definition, off - disables resize of the volumes. Default is "ebs". + changes PVC definition, off - disables resize of the volumes. Default is "pvc". When using OpenShift please use one of the other available options. ## Kubernetes resource requests diff --git a/docs/user.md b/docs/user.md index 7552dca9d..3463983f7 100644 --- a/docs/user.md +++ b/docs/user.md @@ -275,9 +275,18 @@ Postgres clusters are associated with one team by providing the `teamID` in the manifest. Additional superuser teams can be configured as mentioned in the previous paragraph. However, this is a global setting. To assign additional teams, superuser teams and single users to clusters of a given -team, use the [PostgresTeam CRD](../manifests/postgresteam.yaml). It provides -a simple mapping structure. +team, use the [PostgresTeam CRD](../manifests/postgresteam.yaml). +Note, by default the `PostgresTeam` support is disabled in the configuration. +Switch `enable_postgres_team_crd` flag to `true` and the operator will start to +watch for this CRD. Make sure, the cluster role is up to date and contains a +section for [PostgresTeam](../manifests/operator-service-account-rbac.yaml#L30). + +#### Additional teams + +To assign additional teams and single users to clusters of a given team, +define a mapping with the `PostgresTeam` Kubernetes resource. The Postgres +Operator will read such team mappings each time it syncs all Postgres clusters. ```yaml apiVersion: "acid.zalan.do/v1" @@ -285,55 +294,118 @@ kind: PostgresTeam metadata: name: custom-team-membership spec: - additionalSuperuserTeams: - acid: - - "postgres_superusers" additionalTeams: - acid: [] - additionalMembers: - acid: - - "elephant" + a-team: + - "b-team" ``` -One `PostgresTeam` resource could contain mappings of multiple teams but you -can choose to create separate CRDs, alternatively. On each CRD creation or -update the operator will gather all mappings to create additional human users -in databases the next time they are synced. Additional teams are resolved -transitively, meaning you will also add users for their `additionalTeams` -or (not and) `additionalSuperuserTeams`. +With the example above the operator will create login roles for all members +of `b-team` in every cluster owned by `a-team`. It's possible to do vice versa +for clusters of `b-team` in one manifest: -For each additional team the Teams API would be queried. Additional members -will be added either way. There can be "virtual teams" that do not exists in -your Teams API but users of associated teams as well as members will get -created. With `PostgresTeams` it's also easy to cover team name changes. Just -add the mapping between old and new team name and the rest can stay the same. +```yaml +spec: + additionalTeams: + a-team: + - "b-team" + b-team: + - "a-team" +``` + +You see, the `PostgresTeam` CRD is a global team mapping and independent from +the Postgres manifests. It is possible to define multiple mappings, even with +redundant content - the Postgres operator will create one internal cache from +it. Additional teams are resolved transitively, meaning you will also add +users for their `additionalTeams`, e.g.: + +```yaml +spec: + additionalTeams: + a-team: + - "b-team" + - "c-team" + b-team: + - "a-team" +``` + +This creates roles for members of the `c-team` team not only in all clusters +owned by `a-team`, but as well in cluster owned by `b-team`, as `a-team` is +an `additionalTeam` to `b-team` + +Not, you can also define `additionalSuperuserTeams` in the `PostgresTeam` +manifest. By default, this option is disabled and must be configured with +`enable_postgres_team_crd_superusers` to make it work. + +#### Virtual teams + +There can be "virtual teams" that do not exist in the Teams API. It can make +it easier to map a group of teams to many other teams: + +```yaml +spec: + additionalTeams: + a-team: + - "virtual-team" + b-team: + - "virtual-team" + virtual-team: + - "c-team" + - "d-team" +``` + +This example would create roles for members of `c-team` and `d-team` plus +additional `virtual-team` members in clusters owned by `a-team` or `b-team`. + +#### Teams changing their names + +With `PostgresTeams` it is also easy to cover team name changes. Just add +the mapping between old and new team name and the rest can stay the same. +E.g. if team `a-team`'s name would change to `f-team` in the teams API it +could be reflected in a `PostgresTeam` mapping with just two lines: + +```yaml +spec: + additionalTeams: + a-team: + - "f-team" +``` + +This is helpful, because Postgres cluster names are immutable and can not +be changed. Only via cloning it could get a different name starting with the +new `teamID`. + +#### Additional members + +Single members might be excluded from teams although they continue to work +with the same people. However, the teams API would not reflect this anymore. +To still add a database role for former team members list their role under +the `additionalMembers` section of the `PostgresTeam` resource: ```yaml apiVersion: "acid.zalan.do/v1" kind: PostgresTeam metadata: - name: virtualteam-membership + name: custom-team-membership spec: - additionalSuperuserTeams: - acid: - - "virtual_superusers" - virtual_superusers: - - "real_teamA" - - "real_teamB" - real_teamA: - - "real_teamA_renamed" - additionalTeams: - real_teamA: - - "real_teamA_renamed" additionalMembers: - virtual_superusers: - - "foo" + a-team: + - "tia" ``` -Note, by default the `PostgresTeam` support is disabled in the configuration. -Switch `enable_postgres_team_crd` flag to `true` and the operator will start to -watch for this CRD. Make sure, the cluster role is up to date and contains a -section for [PostgresTeam](../manifests/operator-service-account-rbac.yaml#L30). +This will create the login role `tia` in every cluster owned by `a-team`. +The user can connect to databases like the other team members. + +The `additionalMembers` map can also be used to define users of virtual +teams, e.g. for `virtual-team` we used above: + +```yaml +spec: + additionalMembers: + virtual-team: + - "flynch" + - "rdecker" + - "briggs" +``` ## Prepared databases with roles and default privileges diff --git a/e2e/run.sh b/e2e/run.sh index 0024a2569..0f9e6c170 100755 --- a/e2e/run.sh +++ b/e2e/run.sh @@ -8,7 +8,7 @@ IFS=$'\n\t' readonly cluster_name="postgres-operator-e2e-tests" readonly kubeconfig_path="/tmp/kind-config-${cluster_name}" -readonly spilo_image="registry.opensource.zalan.do/acid/spilo-12:1.6-p5" +readonly spilo_image="registry.opensource.zalan.do/acid/spilo-13:2.0-p1" readonly e2e_test_runner_image="registry.opensource.zalan.do/acid/postgres-operator-e2e-tests-runner:0.3" export GOPATH=${GOPATH-~/go} diff --git a/e2e/tests/test_e2e.py b/e2e/tests/test_e2e.py index fa889047c..892efa926 100644 --- a/e2e/tests/test_e2e.py +++ b/e2e/tests/test_e2e.py @@ -11,8 +11,8 @@ from kubernetes import client from tests.k8s_api import K8s from kubernetes.client.rest import ApiException -SPILO_CURRENT = "registry.opensource.zalan.do/acid/spilo-12:1.6-p5" -SPILO_LAZY = "registry.opensource.zalan.do/acid/spilo-cdp-12:1.6-p114" +SPILO_CURRENT = "registry.opensource.zalan.do/acid/spilo-13:2.0-p1" +SPILO_LAZY = "registry.opensource.zalan.do/acid/spilo-cdp-13:2.0-p145" def to_selector(labels): diff --git a/manifests/complete-postgres-manifest.yaml b/manifests/complete-postgres-manifest.yaml index 2555ed450..1dcc20de7 100644 --- a/manifests/complete-postgres-manifest.yaml +++ b/manifests/complete-postgres-manifest.yaml @@ -36,7 +36,7 @@ spec: defaultRoles: true defaultUsers: false postgresql: - version: "12" + version: "13" parameters: # Expert section shared_buffers: "32MB" max_connections: "10" @@ -93,9 +93,9 @@ spec: 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 +# pg_hba: +# - hostssl all all 0.0.0.0/0 md5 +# - host all all 0.0.0.0/0 md5 # slots: # permanent_physical_1: # type: physical diff --git a/manifests/configmap.yaml b/manifests/configmap.yaml index 293404e99..d203ef83e 100644 --- a/manifests/configmap.yaml +++ b/manifests/configmap.yaml @@ -31,7 +31,7 @@ data: # default_memory_request: 100Mi # delete_annotation_date_key: delete-date # delete_annotation_name_key: delete-clustername - docker_image: registry.opensource.zalan.do/acid/spilo-12:1.6-p5 + docker_image: registry.opensource.zalan.do/acid/spilo-13:2.0-p1 # downscaler_annotations: "deployment-time,downscaler/*" # enable_admin_role_for_users: "true" # enable_crd_validation: "true" @@ -41,16 +41,16 @@ data: # enable_init_containers: "true" # enable_lazy_spilo_upgrade: "false" enable_master_load_balancer: "false" - # enable_pgversion_env_var: "false" + enable_pgversion_env_var: "true" # enable_pod_antiaffinity: "false" # enable_pod_disruption_budget: "true" # enable_postgres_team_crd: "false" # enable_postgres_team_crd_superusers: "false" enable_replica_load_balancer: "false" # enable_shm_volume: "true" - # enable_pgversion_env_var: "false" + enable_pgversion_env_var: "true" # enable_sidecars: "true" - enable_spilo_wal_path_compat: "false" + enable_spilo_wal_path_compat: "true" # enable_team_superuser: "false" enable_teams_api: "false" # etcd_host: "" @@ -122,4 +122,4 @@ data: # wal_gs_bucket: "" # wal_s3_bucket: "" watched_namespace: "*" # listen to all namespaces - workers: "8" + workers: "16" diff --git a/manifests/minimal-postgres-manifest.yaml b/manifests/minimal-postgres-manifest.yaml index 4dd6b7ee4..ff96e392b 100644 --- a/manifests/minimal-postgres-manifest.yaml +++ b/manifests/minimal-postgres-manifest.yaml @@ -18,4 +18,4 @@ spec: preparedDatabases: bar: {} postgresql: - version: "12" + version: "13" diff --git a/manifests/operatorconfiguration.crd.yaml b/manifests/operatorconfiguration.crd.yaml index 5ac955913..50405a8cc 100644 --- a/manifests/operatorconfiguration.crd.yaml +++ b/manifests/operatorconfiguration.crd.yaml @@ -315,6 +315,10 @@ spec: properties: logical_backup_docker_image: type: string + logical_backup_google_application_credentials: + type: string + logical_backup_provider: + type: string logical_backup_s3_access_key_id: type: string logical_backup_s3_bucket: diff --git a/manifests/postgresql-operator-default-configuration.yaml b/manifests/postgresql-operator-default-configuration.yaml index 7ab6c35a9..170fc50ff 100644 --- a/manifests/postgresql-operator-default-configuration.yaml +++ b/manifests/postgresql-operator-default-configuration.yaml @@ -6,7 +6,7 @@ configuration: docker_image: registry.opensource.zalan.do/acid/spilo-12:1.6-p3 # enable_crd_validation: true # enable_lazy_spilo_upgrade: false - # enable_pgversion_env_var: false + enable_pgversion_env_var: true # enable_shm_volume: true enable_spilo_wal_path_compat: false etcd_host: "" diff --git a/mkdocs.yml b/mkdocs.yml index 34f55fac8..b8e8c3e04 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -13,4 +13,3 @@ nav: - Config parameters: 'reference/operator_parameters.md' - Manifest parameters: 'reference/cluster_manifest.md' - CLI options and environment: 'reference/command_line_and_environment.md' - - Google Summer of Code 2019: 'gsoc-2019/ideas.md' diff --git a/pkg/apis/acid.zalan.do/v1/crds.go b/pkg/apis/acid.zalan.do/v1/crds.go index 852a2f961..3d4ff09bc 100644 --- a/pkg/apis/acid.zalan.do/v1/crds.go +++ b/pkg/apis/acid.zalan.do/v1/crds.go @@ -1291,6 +1291,12 @@ var OperatorConfigCRDResourceValidation = apiextv1.CustomResourceValidation{ "logical_backup_docker_image": { Type: "string", }, + "logical_backup_google_application_credentials": { + Type: "string", + }, + "logical_backup_provider": { + Type: "string", + }, "logical_backup_s3_access_key_id": { Type: "string", }, diff --git a/pkg/cluster/connection_pooler_test.go b/pkg/cluster/connection_pooler_test.go index 39e0ba9ba..54be0f5bd 100644 --- a/pkg/cluster/connection_pooler_test.go +++ b/pkg/cluster/connection_pooler_test.go @@ -778,7 +778,7 @@ func TestConnectionPoolerDeploymentSpec(t *testing.T) { }, expected: nil, cluster: cluster, - check: testDeploymentOwnwerReference, + check: testDeploymentOwnerReference, }, { subTest: "selector", @@ -931,7 +931,7 @@ func TestConnectionPoolerServiceSpec(t *testing.T) { ConnectionPooler: &acidv1.ConnectionPooler{}, }, cluster: cluster, - check: testServiceOwnwerReference, + check: testServiceOwnerReference, }, { subTest: "selector", diff --git a/pkg/cluster/k8sres_test.go b/pkg/cluster/k8sres_test.go index 8a5103cbe..ec2898d81 100644 --- a/pkg/cluster/k8sres_test.go +++ b/pkg/cluster/k8sres_test.go @@ -939,7 +939,7 @@ func testCustomPodTemplate(cluster *Cluster, podSpec *v1.PodTemplateSpec) error return nil } -func testDeploymentOwnwerReference(cluster *Cluster, deployment *appsv1.Deployment) error { +func testDeploymentOwnerReference(cluster *Cluster, deployment *appsv1.Deployment) error { owner := deployment.ObjectMeta.OwnerReferences[0] if owner.Name != cluster.Statefulset.ObjectMeta.Name { @@ -950,7 +950,7 @@ func testDeploymentOwnwerReference(cluster *Cluster, deployment *appsv1.Deployme return nil } -func testServiceOwnwerReference(cluster *Cluster, service *v1.Service, role PostgresRole) error { +func testServiceOwnerReference(cluster *Cluster, service *v1.Service, role PostgresRole) error { owner := service.ObjectMeta.OwnerReferences[0] if owner.Name != cluster.Statefulset.ObjectMeta.Name { diff --git a/pkg/controller/operator_config.go b/pkg/controller/operator_config.go index 250705656..17f13351d 100644 --- a/pkg/controller/operator_config.go +++ b/pkg/controller/operator_config.go @@ -70,7 +70,7 @@ func (c *Controller) importConfigurationFromCRD(fromCRD *acidv1.OperatorConfigur result.WatchedNamespace = fromCRD.Kubernetes.WatchedNamespace result.PDBNameFormat = fromCRD.Kubernetes.PDBNameFormat result.EnablePodDisruptionBudget = util.CoalesceBool(fromCRD.Kubernetes.EnablePodDisruptionBudget, util.True()) - result.StorageResizeMode = util.Coalesce(fromCRD.Kubernetes.StorageResizeMode, "ebs") + result.StorageResizeMode = util.Coalesce(fromCRD.Kubernetes.StorageResizeMode, "pvc") result.EnableInitContainers = util.CoalesceBool(fromCRD.Kubernetes.EnableInitContainers, util.True()) result.EnableSidecars = util.CoalesceBool(fromCRD.Kubernetes.EnableSidecars, util.True()) result.SecretNameTemplate = fromCRD.Kubernetes.SecretNameTemplate diff --git a/pkg/util/config/config.go b/pkg/util/config/config.go index acc00b2e8..622dee0fb 100644 --- a/pkg/util/config/config.go +++ b/pkg/util/config/config.go @@ -182,7 +182,7 @@ type Config struct { CustomPodAnnotations map[string]string `name:"custom_pod_annotations"` EnablePodAntiAffinity bool `name:"enable_pod_antiaffinity" default:"false"` PodAntiAffinityTopologyKey string `name:"pod_antiaffinity_topology_key" default:"kubernetes.io/hostname"` - StorageResizeMode string `name:"storage_resize_mode" default:"ebs"` + StorageResizeMode string `name:"storage_resize_mode" default:"pvc"` EnableLoadBalancer *bool `name:"enable_load_balancer"` // deprecated and kept for backward compatibility ExternalTrafficPolicy string `name:"external_traffic_policy" default:"Cluster"` MasterDNSNameFormat StringTemplate `name:"master_dns_name_format" default:"{cluster}.{team}.{hostedzone}"` @@ -202,7 +202,7 @@ type Config struct { PostgresSuperuserTeams []string `name:"postgres_superuser_teams" default:""` SetMemoryRequestToLimit bool `name:"set_memory_request_to_limit" default:"false"` EnableLazySpiloUpgrade bool `name:"enable_lazy_spilo_upgrade" default:"false"` - EnablePgVersionEnvVar bool `name:"enable_pgversion_env_var" default:"false"` + EnablePgVersionEnvVar bool `name:"enable_pgversion_env_var" default:"true"` EnableSpiloWalPathCompat bool `name:"enable_spilo_wal_path_compat" default:"false"` } diff --git a/ui/manifests/deployment.yaml b/ui/manifests/deployment.yaml index 4161b4fc1..976da53f2 100644 --- a/ui/manifests/deployment.yaml +++ b/ui/manifests/deployment.yaml @@ -68,10 +68,8 @@ spec: "cost_core": 0.0575, "cost_memory": 0.014375, "postgresql_versions": [ + "13", "12", - "11", - "10", - "9.6", - "9.5" + "11" ] } diff --git a/ui/operator_ui/main.py b/ui/operator_ui/main.py index 893e45ef0..5fbb6d24e 100644 --- a/ui/operator_ui/main.py +++ b/ui/operator_ui/main.py @@ -303,7 +303,7 @@ DEFAULT_UI_CONFIG = { 'users_visible': True, 'databases_visible': True, 'resources_visible': True, - 'postgresql_versions': ['9.6', '10', '11'], + 'postgresql_versions': ['11','12','13'], 'dns_format_string': '{0}.{1}.{2}', 'pgui_link': '', 'static_network_whitelist': {}, From 07c4f52eded02dd140e0c45e032fcc22582e149d Mon Sep 17 00:00:00 2001 From: Felix Kunde Date: Fri, 18 Dec 2020 12:07:59 +0100 Subject: [PATCH 03/35] use pointer type for nodeAffinity (#1263) * use pointer type for nodeAffinity --- pkg/apis/acid.zalan.do/v1/postgresql_type.go | 2 +- pkg/apis/acid.zalan.do/v1/zz_generated.deepcopy.go | 6 +++++- pkg/cluster/cluster.go | 6 +++--- pkg/cluster/connection_pooler.go | 2 +- pkg/cluster/k8sres.go | 2 +- pkg/cluster/k8sres_test.go | 2 +- 6 files changed, 12 insertions(+), 8 deletions(-) diff --git a/pkg/apis/acid.zalan.do/v1/postgresql_type.go b/pkg/apis/acid.zalan.do/v1/postgresql_type.go index 0c87f96f8..bdae22a7c 100644 --- a/pkg/apis/acid.zalan.do/v1/postgresql_type.go +++ b/pkg/apis/acid.zalan.do/v1/postgresql_type.go @@ -61,7 +61,7 @@ type PostgresSpec struct { Databases map[string]string `json:"databases,omitempty"` PreparedDatabases map[string]PreparedDatabase `json:"preparedDatabases,omitempty"` SchedulerName *string `json:"schedulerName,omitempty"` - NodeAffinity v1.NodeAffinity `json:"nodeAffinity,omitempty"` + NodeAffinity *v1.NodeAffinity `json:"nodeAffinity,omitempty"` Tolerations []v1.Toleration `json:"tolerations,omitempty"` Sidecars []Sidecar `json:"sidecars,omitempty"` InitContainers []v1.Container `json:"initContainers,omitempty"` diff --git a/pkg/apis/acid.zalan.do/v1/zz_generated.deepcopy.go b/pkg/apis/acid.zalan.do/v1/zz_generated.deepcopy.go index 83cd18c40..2f4104ce9 100644 --- a/pkg/apis/acid.zalan.do/v1/zz_generated.deepcopy.go +++ b/pkg/apis/acid.zalan.do/v1/zz_generated.deepcopy.go @@ -633,7 +633,11 @@ func (in *PostgresSpec) DeepCopyInto(out *PostgresSpec) { *out = new(string) **out = **in } - in.NodeAffinity.DeepCopyInto(&out.NodeAffinity) + if in.NodeAffinity != nil { + in, out := &in.NodeAffinity, &out.NodeAffinity + *out = new(corev1.NodeAffinity) + (*in).DeepCopyInto(*out) + } if in.Tolerations != nil { in, out := &in.Tolerations, &out.Tolerations *out = make([]corev1.Toleration, len(*in)) diff --git a/pkg/cluster/cluster.go b/pkg/cluster/cluster.go index 050aeb5b5..42515a7c0 100644 --- a/pkg/cluster/cluster.go +++ b/pkg/cluster/cluster.go @@ -113,9 +113,9 @@ func New(cfg Config, kubeClient k8sutil.KubernetesClient, pgSpec acidv1.Postgres return fmt.Sprintf("%s-%s", e.PodName, e.ResourceVersion), nil }) - password_encryption, ok := pgSpec.Spec.PostgresqlParam.Parameters["password_encryption"] + passwordEncryption, ok := pgSpec.Spec.PostgresqlParam.Parameters["password_encryption"] if !ok { - password_encryption = "md5" + passwordEncryption = "md5" } cluster := &Cluster{ @@ -128,7 +128,7 @@ func New(cfg Config, kubeClient k8sutil.KubernetesClient, pgSpec acidv1.Postgres Secrets: make(map[types.UID]*v1.Secret), Services: make(map[PostgresRole]*v1.Service), Endpoints: make(map[PostgresRole]*v1.Endpoints)}, - userSyncStrategy: users.DefaultUserSyncStrategy{PasswordEncryption: password_encryption}, + userSyncStrategy: users.DefaultUserSyncStrategy{PasswordEncryption: passwordEncryption}, deleteOptions: metav1.DeleteOptions{PropagationPolicy: &deletePropagationPolicy}, podEventsQueue: podEventsQueue, KubeClient: kubeClient, diff --git a/pkg/cluster/connection_pooler.go b/pkg/cluster/connection_pooler.go index 82b855bf2..1d1d609e4 100644 --- a/pkg/cluster/connection_pooler.go +++ b/pkg/cluster/connection_pooler.go @@ -22,7 +22,7 @@ import ( "github.com/zalando/postgres-operator/pkg/util/k8sutil" ) -// K8S objects that are belong to connection pooler +// ConnectionPoolerObjects K8s objects that are belong to connection pooler type ConnectionPoolerObjects struct { Deployment *appsv1.Deployment Service *v1.Service diff --git a/pkg/cluster/k8sres.go b/pkg/cluster/k8sres.go index 1650d38d3..6b47b37f6 100644 --- a/pkg/cluster/k8sres.go +++ b/pkg/cluster/k8sres.go @@ -1223,7 +1223,7 @@ func (c *Cluster) generateStatefulSet(spec *acidv1.PostgresSpec) (*appsv1.Statef effectiveRunAsUser, effectiveRunAsGroup, effectiveFSGroup, - nodeAffinity(c.OpConfig.NodeReadinessLabel, &spec.NodeAffinity), + nodeAffinity(c.OpConfig.NodeReadinessLabel, spec.NodeAffinity), spec.SchedulerName, int64(c.OpConfig.PodTerminateGracePeriod.Seconds()), c.OpConfig.PodServiceAccountName, diff --git a/pkg/cluster/k8sres_test.go b/pkg/cluster/k8sres_test.go index ec2898d81..e880fcc3b 100644 --- a/pkg/cluster/k8sres_test.go +++ b/pkg/cluster/k8sres_test.go @@ -882,7 +882,7 @@ func TestNodeAffinity(t *testing.T) { Volume: acidv1.Volume{ Size: "1G", }, - NodeAffinity: *nodeAffinity, + NodeAffinity: nodeAffinity, } } From 102178409b794288906f7292481160ae1c6a3bfd Mon Sep 17 00:00:00 2001 From: Felix Kunde Date: Fri, 18 Dec 2020 13:10:35 +0100 Subject: [PATCH 04/35] bump tp v1.6.0 (#1265) * bump tp v1.6.0 * update logical-backup image * Using smaller image for e2e test. * fix env var name in docs * add postgresql-client-13 to logical backup image Co-authored-by: Jan Mussler --- .../postgres-operator-issue-template.md | 2 +- README.md | 16 +++--- charts/postgres-operator-ui/Chart.yaml | 4 +- charts/postgres-operator-ui/index.yaml | 52 +++++++++--------- .../postgres-operator-ui-1.4.0.tgz | Bin 3517 -> 0 bytes .../postgres-operator-ui-1.6.0.tgz | Bin 0 -> 3900 bytes .../templates/deployment.yaml | 6 +- charts/postgres-operator-ui/values.yaml | 2 +- charts/postgres-operator/Chart.yaml | 4 +- charts/postgres-operator/index.yaml | 48 ++++++++-------- .../postgres-operator-1.4.0.tgz | Bin 14223 -> 0 bytes .../postgres-operator-1.6.0.tgz | Bin 0 -> 18415 bytes charts/postgres-operator/values-crd.yaml | 6 +- charts/postgres-operator/values.yaml | 6 +- delivery.yaml | 4 +- docker/logical-backup/Dockerfile | 1 + docs/administrator.md | 2 +- e2e/run.sh | 2 +- e2e/tests/test_e2e.py | 6 +- manifests/complete-postgres-manifest.yaml | 2 +- manifests/configmap.yaml | 5 +- manifests/postgres-operator.yaml | 2 +- ...gresql-operator-default-configuration.yaml | 4 +- manifests/standby-manifest.yaml | 2 +- pkg/controller/operator_config.go | 2 +- pkg/util/config/config.go | 2 +- ui/manifests/deployment.yaml | 2 +- ui/run_local.sh | 6 +- 28 files changed, 91 insertions(+), 97 deletions(-) delete mode 100644 charts/postgres-operator-ui/postgres-operator-ui-1.4.0.tgz create mode 100644 charts/postgres-operator-ui/postgres-operator-ui-1.6.0.tgz delete mode 100644 charts/postgres-operator/postgres-operator-1.4.0.tgz create mode 100644 charts/postgres-operator/postgres-operator-1.6.0.tgz diff --git a/.github/ISSUE_TEMPLATE/postgres-operator-issue-template.md b/.github/ISSUE_TEMPLATE/postgres-operator-issue-template.md index ff7567d2d..a4dec9409 100644 --- a/.github/ISSUE_TEMPLATE/postgres-operator-issue-template.md +++ b/.github/ISSUE_TEMPLATE/postgres-operator-issue-template.md @@ -9,7 +9,7 @@ assignees: '' Please, answer some short questions which should help us to understand your problem / question better? -- **Which image of the operator are you using?** e.g. registry.opensource.zalan.do/acid/postgres-operator:v1.5.0 +- **Which image of the operator are you using?** e.g. registry.opensource.zalan.do/acid/postgres-operator:v1.6.0 - **Where do you run it - cloud or metal? Kubernetes or OpenShift?** [AWS K8s | GCP ... | Bare Metal K8s] - **Are you running Postgres Operator in production?** [yes | no] - **Type of issue?** [Bug report, question, feature request, etc.] diff --git a/README.md b/README.md index 465e726d4..7edb60d84 100644 --- a/README.md +++ b/README.md @@ -14,21 +14,21 @@ pipelines with no access to Kubernetes API directly, promoting infrastructure as ### Operator features * Rolling updates on Postgres cluster changes, incl. quick minor version updates -* Live volume resize without pod restarts (AWS EBS, PvC) +* Live volume resize without pod restarts (AWS EBS, PVC) * Database connection pooler with PGBouncer * Restore and cloning Postgres clusters (incl. major version upgrade) * Additionally logical backups to S3 bucket can be configured * Standby cluster from S3 WAL archive * Configurable for non-cloud environments * Basic credential and user management on K8s, eases application deployments +* Support for custom TLS certificates * UI to create and edit Postgres cluster manifests * Works well on Amazon AWS, Google Cloud, OpenShift and locally on Kind -* Support for custom TLS certificates * Base support for AWS EBS gp3 migration (iops, throughput pending) ### PostgreSQL features -* Supports PostgreSQL 13, starting from 9.6+ +* Supports PostgreSQL 13, starting from 9.5+ * Streaming replication cluster via Patroni * Point-In-Time-Recovery with [pg_basebackup](https://www.postgresql.org/docs/11/app-pgbasebackup.html) / @@ -59,13 +59,13 @@ If you are new to the operator, you can skip this and just start using the Postg The Postgres operator supports Postgres 13 with the new Spilo Image that includes also the recent Patroni version to support PG13 settings. More work on optimizing restarts and rolling upgrades is pending. -If you are already using the Postgres operator in older version with a Spilo 12 Docker Image you need to be aware of the changes for the backup path. -We introduce the major version into the backup path to smooth the major version upgrade that is now supported manually. +If you are already using the Postgres operator in older version with a Spilo 12 Docker image you need to be aware of the changes for the backup path. +We introduce the major version into the backup path to smoothen the [major version upgrade](docs/administrator.md#minor-and-major-version-upgrade) that is now supported manually. -The new operator configuration, sets a compatilibty flag *enable_spilo_wal_path_compat* to make Spilo look in current path but also old format paths for wal segments. -This comes at potential perf. costs, and should be disabled after a few days. +The new operator configuration can set a compatibility flag *enable_spilo_wal_path_compat* to make Spilo look for wal segments in the current path but also old format paths. +This comes at potential performance costs and should be disabled after a few days. -The new Spilo 13 image is: `registry.opensource.zalan.do/acid/spilo-13:2.0-p1` +The new Spilo 13 image is: `registry.opensource.zalan.do/acid/spilo-13:2.0-p2` The last Spilo 12 image is: `registry.opensource.zalan.do/acid/spilo-12:1.6-p5` diff --git a/charts/postgres-operator-ui/Chart.yaml b/charts/postgres-operator-ui/Chart.yaml index 13550d67e..9be6c84dd 100644 --- a/charts/postgres-operator-ui/Chart.yaml +++ b/charts/postgres-operator-ui/Chart.yaml @@ -1,7 +1,7 @@ apiVersion: v1 name: postgres-operator-ui -version: 1.5.0 -appVersion: 1.5.0 +version: 1.6.0 +appVersion: 1.6.0 home: https://github.com/zalando/postgres-operator description: Postgres Operator UI provides a graphical interface for a convenient database-as-a-service user experience keywords: diff --git a/charts/postgres-operator-ui/index.yaml b/charts/postgres-operator-ui/index.yaml index 5a7b42d80..7d1f5fc1c 100644 --- a/charts/postgres-operator-ui/index.yaml +++ b/charts/postgres-operator-ui/index.yaml @@ -1,9 +1,32 @@ apiVersion: v1 entries: postgres-operator-ui: + - apiVersion: v1 + appVersion: 1.6.0 + created: "2020-12-17T15:49:56.570324588+01:00" + description: Postgres Operator UI provides a graphical interface for a convenient + database-as-a-service user experience + digest: 9ce86d53b4e79dc405aea5fe2feadd163dfbbde43205782c20206ac0ba9d5e4d + home: https://github.com/zalando/postgres-operator + keywords: + - postgres + - operator + - ui + - cloud-native + - patroni + - spilo + maintainers: + - email: opensource@zalando.de + name: Zalando + name: postgres-operator-ui + sources: + - https://github.com/zalando/postgres-operator + urls: + - postgres-operator-ui-1.6.0.tgz + version: 1.6.0 - apiVersion: v1 appVersion: 1.5.0 - created: "2020-06-04T17:06:37.153522579+02:00" + created: "2020-12-17T15:49:56.569780943+01:00" description: Postgres Operator UI provides a graphical interface for a convenient database-as-a-service user experience digest: c91ea39e6d51d57f4048fb1b6ec53b40823f2690eb88e4e4f1a036367b9fdd61 @@ -24,29 +47,4 @@ entries: urls: - postgres-operator-ui-1.5.0.tgz version: 1.5.0 - - apiVersion: v1 - appVersion: 1.4.0 - created: "2020-06-04T17:06:37.15302073+02:00" - description: Postgres Operator UI provides a graphical interface for a convenient - database-as-a-service user experience - digest: 00e0eff7056d56467cd5c975657fbb76c8d01accd25a4b7aca81bc42aeac961d - home: https://github.com/zalando/postgres-operator - keywords: - - postgres - - operator - - ui - - cloud-native - - patroni - - spilo - maintainers: - - email: opensource@zalando.de - name: Zalando - - email: sk@sik-net.de - name: siku4 - name: postgres-operator-ui - sources: - - https://github.com/zalando/postgres-operator - urls: - - postgres-operator-ui-1.4.0.tgz - version: 1.4.0 -generated: "2020-06-04T17:06:37.152369987+02:00" +generated: "2020-12-17T15:49:56.569108956+01:00" diff --git a/charts/postgres-operator-ui/postgres-operator-ui-1.4.0.tgz b/charts/postgres-operator-ui/postgres-operator-ui-1.4.0.tgz deleted file mode 100644 index 8d1276dd16ab28ad5d176c810c86ad617c682359..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3517 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 K46+OWRsaC)$fq&@ literal 0 HcmV?d00001 diff --git a/charts/postgres-operator-ui/templates/deployment.yaml b/charts/postgres-operator-ui/templates/deployment.yaml index 5733a1c35..29bf2e670 100644 --- a/charts/postgres-operator-ui/templates/deployment.yaml +++ b/charts/postgres-operator-ui/templates/deployment.yaml @@ -68,10 +68,8 @@ spec: "resources_visible": true, "users_visible": true, "postgresql_versions": [ + "13", "12", - "11", - "10", - "9.6", - "9.5" + "11" ] } diff --git a/charts/postgres-operator-ui/values.yaml b/charts/postgres-operator-ui/values.yaml index 55c1d2452..4dc6388ac 100644 --- a/charts/postgres-operator-ui/values.yaml +++ b/charts/postgres-operator-ui/values.yaml @@ -8,7 +8,7 @@ replicaCount: 1 image: registry: registry.opensource.zalan.do repository: acid/postgres-operator-ui - tag: v1.5.0-dirty + tag: v1.6.0 pullPolicy: "IfNotPresent" # Optionally specify an array of imagePullSecrets. diff --git a/charts/postgres-operator/Chart.yaml b/charts/postgres-operator/Chart.yaml index cd9f75586..e5a66b6e3 100644 --- a/charts/postgres-operator/Chart.yaml +++ b/charts/postgres-operator/Chart.yaml @@ -1,7 +1,7 @@ apiVersion: v1 name: postgres-operator -version: 1.5.0 -appVersion: 1.5.0 +version: 1.6.0 +appVersion: 1.6.0 home: https://github.com/zalando/postgres-operator description: Postgres Operator creates and manages PostgreSQL clusters running in Kubernetes keywords: diff --git a/charts/postgres-operator/index.yaml b/charts/postgres-operator/index.yaml index 3c62625a1..6b64fd705 100644 --- a/charts/postgres-operator/index.yaml +++ b/charts/postgres-operator/index.yaml @@ -1,9 +1,31 @@ apiVersion: v1 entries: postgres-operator: + - apiVersion: v1 + appVersion: 1.6.0 + created: "2020-12-17T16:16:25.639708821+01:00" + description: Postgres Operator creates and manages PostgreSQL clusters running + in Kubernetes + digest: 2f5f527bae0a22b02f2f7b1e2352665cecf489a990e18212444fa34450b97604 + home: https://github.com/zalando/postgres-operator + keywords: + - postgres + - operator + - cloud-native + - patroni + - spilo + maintainers: + - email: opensource@zalando.de + name: Zalando + name: postgres-operator + sources: + - https://github.com/zalando/postgres-operator + urls: + - postgres-operator-1.6.0.tgz + version: 1.6.0 - apiVersion: v1 appVersion: 1.5.0 - created: "2020-06-04T17:06:49.41741489+02:00" + created: "2020-12-17T16:16:25.637262877+01:00" description: Postgres Operator creates and manages PostgreSQL clusters running in Kubernetes digest: 198351d5db52e65cdf383d6f3e1745d91ac1e2a01121f8476f8b1be728b09531 @@ -23,26 +45,4 @@ entries: urls: - postgres-operator-1.5.0.tgz version: 1.5.0 - - apiVersion: v1 - appVersion: 1.4.0 - created: "2020-06-04T17:06:49.416001109+02:00" - description: Postgres Operator creates and manages PostgreSQL clusters running - in Kubernetes - digest: f8b90fecfc3cb825b94ed17edd9d5cefc36ae61801d4568597b4a79bcd73b2e9 - home: https://github.com/zalando/postgres-operator - keywords: - - postgres - - operator - - cloud-native - - patroni - - spilo - maintainers: - - email: opensource@zalando.de - name: Zalando - name: postgres-operator - sources: - - https://github.com/zalando/postgres-operator - urls: - - postgres-operator-1.4.0.tgz - version: 1.4.0 -generated: "2020-06-04T17:06:49.414521538+02:00" +generated: "2020-12-17T16:16:25.635647131+01:00" diff --git a/charts/postgres-operator/postgres-operator-1.4.0.tgz b/charts/postgres-operator/postgres-operator-1.4.0.tgz deleted file mode 100644 index 88a187374ae3f834013d39371358266e7edc2815..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14223 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 Jflz>g{2%o}nxOyy diff --git a/charts/postgres-operator/postgres-operator-1.6.0.tgz b/charts/postgres-operator/postgres-operator-1.6.0.tgz new file mode 100644 index 0000000000000000000000000000000000000000..ce19d588233b71430c42cf82a76cc994d1123136 GIT binary patch literal 18415 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 Date: Fri, 8 Jan 2021 12:00:23 +0100 Subject: [PATCH 10/35] [UI] on read_pods filter only spilo pods (#1297) --- ui/operator_ui/spiloutils.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/ui/operator_ui/spiloutils.py b/ui/operator_ui/spiloutils.py index 34ca42718..26113bd54 100644 --- a/ui/operator_ui/spiloutils.py +++ b/ui/operator_ui/spiloutils.py @@ -107,6 +107,12 @@ def encode_labels(label_selector): ]) +def cluster_labels(spilo_cluster): + labels = COMMON_CLUSTER_LABEL + labels[OPERATOR_CLUSTER_NAME_LABEL] = spilo_cluster + return labels + + def kubernetes_url( resource_type, namespace='default', @@ -151,7 +157,7 @@ def read_pods(cluster, namespace, spilo_cluster): cluster=cluster, resource_type='pods', namespace=namespace, - label_selector={OPERATOR_CLUSTER_NAME_LABEL: spilo_cluster}, + label_selector=cluster_labels(spilo_cluster), ) From 9d94e018ffc903ba50959ad140a2345bf3c30bd5 Mon Sep 17 00:00:00 2001 From: Pavel Tumik Date: Fri, 8 Jan 2021 03:30:28 -0800 Subject: [PATCH 11/35] fix incorrect tag for logical backup docker image (#1295) --- charts/postgres-operator/values.yaml | 2 +- manifests/postgresql-operator-default-configuration.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/charts/postgres-operator/values.yaml b/charts/postgres-operator/values.yaml index 5683013e5..ebfd49252 100644 --- a/charts/postgres-operator/values.yaml +++ b/charts/postgres-operator/values.yaml @@ -239,7 +239,7 @@ configAwsOrGcp: # configure K8s cron job managed by the operator configLogicalBackup: # image for pods of the logical backup job (example runs pg_dumpall) - logical_backup_docker_image: "registry.opensource.zalan.do/acid/logical-backup:v.1.6.0" + logical_backup_docker_image: "registry.opensource.zalan.do/acid/logical-backup:v1.6.0" # path of google cloud service account json file # logical_backup_google_application_credentials: "" # prefix for the backup job name diff --git a/manifests/postgresql-operator-default-configuration.yaml b/manifests/postgresql-operator-default-configuration.yaml index f011feca7..96394976d 100644 --- a/manifests/postgresql-operator-default-configuration.yaml +++ b/manifests/postgresql-operator-default-configuration.yaml @@ -115,7 +115,7 @@ configuration: # wal_gs_bucket: "" # wal_s3_bucket: "" logical_backup: - logical_backup_docker_image: "registry.opensource.zalan.do/acid/logical-backup:v.1.6.0" + logical_backup_docker_image: "registry.opensource.zalan.do/acid/logical-backup:v1.6.0" # logical_backup_google_application_credentials: "" logical_backup_job_prefix: "logical-backup-" logical_backup_provider: "s3" From b7f4cde541ba959d1519adf544bad39672a303bf Mon Sep 17 00:00:00 2001 From: Sergey Dudoladov Date: Fri, 8 Jan 2021 15:08:44 +0100 Subject: [PATCH 12/35] wrap getting Patroni state into retry (#1293) Retry calls to Patorni API to get cluster state Co-authored-by: Sergey Dudoladov --- pkg/cluster/pod.go | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/pkg/cluster/pod.go b/pkg/cluster/pod.go index a13eb479c..cf43de9a7 100644 --- a/pkg/cluster/pod.go +++ b/pkg/cluster/pod.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "math/rand" + "time" appsv1 "k8s.io/api/apps/v1" v1 "k8s.io/api/core/v1" @@ -11,6 +12,7 @@ import ( "github.com/zalando/postgres-operator/pkg/spec" "github.com/zalando/postgres-operator/pkg/util" + "github.com/zalando/postgres-operator/pkg/util/retryutil" ) func (c *Cluster) listPods() ([]v1.Pod, error) { @@ -309,7 +311,23 @@ func (c *Cluster) isSafeToRecreatePods(pods *v1.PodList) bool { } for _, pod := range pods.Items { - state, err := c.patroni.GetPatroniMemberState(&pod) + + var state string + + err := retryutil.Retry(1*time.Second, 5*time.Second, + func() (bool, error) { + + var err error + + state, err = c.patroni.GetPatroniMemberState(&pod) + + if err != nil { + return false, err + } + return true, nil + }, + ) + if err != nil { c.logger.Errorf("failed to get Patroni state for pod: %s", err) return false From 50cb5898ea715a1db7e634de928b2d16dc8cd969 Mon Sep 17 00:00:00 2001 From: polarclair Date: Fri, 8 Jan 2021 08:46:38 -0600 Subject: [PATCH 13/35] Update workers to the new default (#1284) --- docs/reference/operator_parameters.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/reference/operator_parameters.md b/docs/reference/operator_parameters.md index 76a3cefd4..04d5fe23d 100644 --- a/docs/reference/operator_parameters.md +++ b/docs/reference/operator_parameters.md @@ -126,7 +126,7 @@ Those are top-level keys, containing both leaf keys and groups. * **workers** number of working routines the operator spawns to process requests to - create/update/delete/sync clusters concurrently. The default is `4`. + create/update/delete/sync clusters concurrently. The default is `8`. * **max_instances** operator will cap the number of instances in any managed Postgres cluster up From 2eac36d003cac6c1a8610fa3a0e9e488fd44ea35 Mon Sep 17 00:00:00 2001 From: Felix Kunde Date: Fri, 8 Jan 2021 17:07:28 +0100 Subject: [PATCH 14/35] update year in generated code and maintainer info in Dockerfiles (#1298) * update year in generated code and maintainer info in Dockerfiles * minor update in admin docs --- LICENSE | 2 +- docker/DebugDockerfile | 2 +- docs/administrator.md | 8 ++++---- pkg/apis/acid.zalan.do/v1/zz_generated.deepcopy.go | 2 +- pkg/generated/clientset/versioned/clientset.go | 2 +- pkg/generated/clientset/versioned/doc.go | 2 +- .../clientset/versioned/fake/clientset_generated.go | 2 +- pkg/generated/clientset/versioned/fake/doc.go | 2 +- pkg/generated/clientset/versioned/fake/register.go | 2 +- pkg/generated/clientset/versioned/scheme/doc.go | 2 +- pkg/generated/clientset/versioned/scheme/register.go | 2 +- .../typed/acid.zalan.do/v1/acid.zalan.do_client.go | 2 +- .../clientset/versioned/typed/acid.zalan.do/v1/doc.go | 2 +- .../versioned/typed/acid.zalan.do/v1/fake/doc.go | 2 +- .../acid.zalan.do/v1/fake/fake_acid.zalan.do_client.go | 2 +- .../acid.zalan.do/v1/fake/fake_operatorconfiguration.go | 2 +- .../typed/acid.zalan.do/v1/fake/fake_postgresql.go | 2 +- .../typed/acid.zalan.do/v1/fake/fake_postgresteam.go | 2 +- .../typed/acid.zalan.do/v1/generated_expansion.go | 2 +- .../typed/acid.zalan.do/v1/operatorconfiguration.go | 2 +- .../versioned/typed/acid.zalan.do/v1/postgresql.go | 2 +- .../versioned/typed/acid.zalan.do/v1/postgresteam.go | 2 +- .../informers/externalversions/acid.zalan.do/interface.go | 2 +- .../externalversions/acid.zalan.do/v1/interface.go | 2 +- .../externalversions/acid.zalan.do/v1/postgresql.go | 2 +- .../externalversions/acid.zalan.do/v1/postgresteam.go | 2 +- pkg/generated/informers/externalversions/factory.go | 2 +- pkg/generated/informers/externalversions/generic.go | 2 +- .../internalinterfaces/factory_interfaces.go | 2 +- .../listers/acid.zalan.do/v1/expansion_generated.go | 2 +- pkg/generated/listers/acid.zalan.do/v1/postgresql.go | 2 +- pkg/generated/listers/acid.zalan.do/v1/postgresteam.go | 2 +- ui/Dockerfile | 2 +- 33 files changed, 36 insertions(+), 36 deletions(-) diff --git a/LICENSE b/LICENSE index da62089ec..7c0f459a5 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2020 Zalando SE +Copyright (c) 2021 Zalando SE Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/docker/DebugDockerfile b/docker/DebugDockerfile index 0c11fe3b4..0bfe9a277 100644 --- a/docker/DebugDockerfile +++ b/docker/DebugDockerfile @@ -1,5 +1,5 @@ FROM alpine -MAINTAINER Team ACID @ Zalando +LABEL maintainer="Team ACID @ Zalando " # We need root certificates to deal with teams api over https RUN apk --no-cache add ca-certificates go git musl-dev diff --git a/docs/administrator.md b/docs/administrator.md index 396629b1a..30b612ded 100644 --- a/docs/administrator.md +++ b/docs/administrator.md @@ -30,10 +30,10 @@ To trigger the upgrade, increase the version in the cluster manifest. After Pods are rotated `configure_spilo` will notice the version mismatch and start the old version again. You can then exec into the Postgres container of the master instance and call `python3 /scripts/inplace_upgrade.py N` where `N` -is the number of members of your cluster (see `number_of_instances`). The -upgrade is usually fast, well under one minute for most DBs. Note, that changes -become irrevertible once `pg_upgrade` is called. To understand the upgrade -procedure, refer to the [corresponding PR in Spilo](https://github.com/zalando/spilo/pull/488). +is the number of members of your cluster (see [`numberOfInstances`](https://github.com/zalando/postgres-operator/blob/50cb5898ea715a1db7e634de928b2d16dc8cd969/manifests/minimal-postgres-manifest.yaml#L10)). +The upgrade is usually fast, well under one minute for most DBs. Note, that +changes become irrevertible once `pg_upgrade` is called. To understand the +upgrade procedure, refer to the [corresponding PR in Spilo](https://github.com/zalando/spilo/pull/488). ## CRD Validation diff --git a/pkg/apis/acid.zalan.do/v1/zz_generated.deepcopy.go b/pkg/apis/acid.zalan.do/v1/zz_generated.deepcopy.go index 2f4104ce9..4bcbd2f5e 100644 --- a/pkg/apis/acid.zalan.do/v1/zz_generated.deepcopy.go +++ b/pkg/apis/acid.zalan.do/v1/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2020 Compose, Zalando SE +Copyright 2021 Compose, Zalando SE Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/pkg/generated/clientset/versioned/clientset.go b/pkg/generated/clientset/versioned/clientset.go index 5f1e5880a..ab4a88735 100644 --- a/pkg/generated/clientset/versioned/clientset.go +++ b/pkg/generated/clientset/versioned/clientset.go @@ -1,5 +1,5 @@ /* -Copyright 2020 Compose, Zalando SE +Copyright 2021 Compose, Zalando SE Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/pkg/generated/clientset/versioned/doc.go b/pkg/generated/clientset/versioned/doc.go index 9ec677ac7..ae87609f6 100644 --- a/pkg/generated/clientset/versioned/doc.go +++ b/pkg/generated/clientset/versioned/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2020 Compose, Zalando SE +Copyright 2021 Compose, Zalando SE Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/pkg/generated/clientset/versioned/fake/clientset_generated.go b/pkg/generated/clientset/versioned/fake/clientset_generated.go index 55771905f..6ae5db2d3 100644 --- a/pkg/generated/clientset/versioned/fake/clientset_generated.go +++ b/pkg/generated/clientset/versioned/fake/clientset_generated.go @@ -1,5 +1,5 @@ /* -Copyright 2020 Compose, Zalando SE +Copyright 2021 Compose, Zalando SE Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/pkg/generated/clientset/versioned/fake/doc.go b/pkg/generated/clientset/versioned/fake/doc.go index 7c9574952..bc1c91a11 100644 --- a/pkg/generated/clientset/versioned/fake/doc.go +++ b/pkg/generated/clientset/versioned/fake/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2020 Compose, Zalando SE +Copyright 2021 Compose, Zalando SE Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/pkg/generated/clientset/versioned/fake/register.go b/pkg/generated/clientset/versioned/fake/register.go index c5a24f7da..c4d383aab 100644 --- a/pkg/generated/clientset/versioned/fake/register.go +++ b/pkg/generated/clientset/versioned/fake/register.go @@ -1,5 +1,5 @@ /* -Copyright 2020 Compose, Zalando SE +Copyright 2021 Compose, Zalando SE Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/pkg/generated/clientset/versioned/scheme/doc.go b/pkg/generated/clientset/versioned/scheme/doc.go index 02fd3d592..cd594164b 100644 --- a/pkg/generated/clientset/versioned/scheme/doc.go +++ b/pkg/generated/clientset/versioned/scheme/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2020 Compose, Zalando SE +Copyright 2021 Compose, Zalando SE Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/pkg/generated/clientset/versioned/scheme/register.go b/pkg/generated/clientset/versioned/scheme/register.go index 381948a4a..8be969eb5 100644 --- a/pkg/generated/clientset/versioned/scheme/register.go +++ b/pkg/generated/clientset/versioned/scheme/register.go @@ -1,5 +1,5 @@ /* -Copyright 2020 Compose, Zalando SE +Copyright 2021 Compose, Zalando SE Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/pkg/generated/clientset/versioned/typed/acid.zalan.do/v1/acid.zalan.do_client.go b/pkg/generated/clientset/versioned/typed/acid.zalan.do/v1/acid.zalan.do_client.go index e48e2d2a7..5666201d4 100644 --- a/pkg/generated/clientset/versioned/typed/acid.zalan.do/v1/acid.zalan.do_client.go +++ b/pkg/generated/clientset/versioned/typed/acid.zalan.do/v1/acid.zalan.do_client.go @@ -1,5 +1,5 @@ /* -Copyright 2020 Compose, Zalando SE +Copyright 2021 Compose, Zalando SE Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/pkg/generated/clientset/versioned/typed/acid.zalan.do/v1/doc.go b/pkg/generated/clientset/versioned/typed/acid.zalan.do/v1/doc.go index 55338c4de..eb8fcf1f4 100644 --- a/pkg/generated/clientset/versioned/typed/acid.zalan.do/v1/doc.go +++ b/pkg/generated/clientset/versioned/typed/acid.zalan.do/v1/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2020 Compose, Zalando SE +Copyright 2021 Compose, Zalando SE Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/pkg/generated/clientset/versioned/typed/acid.zalan.do/v1/fake/doc.go b/pkg/generated/clientset/versioned/typed/acid.zalan.do/v1/fake/doc.go index 1ae436a9b..c5fd1c04b 100644 --- a/pkg/generated/clientset/versioned/typed/acid.zalan.do/v1/fake/doc.go +++ b/pkg/generated/clientset/versioned/typed/acid.zalan.do/v1/fake/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2020 Compose, Zalando SE +Copyright 2021 Compose, Zalando SE Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/pkg/generated/clientset/versioned/typed/acid.zalan.do/v1/fake/fake_acid.zalan.do_client.go b/pkg/generated/clientset/versioned/typed/acid.zalan.do/v1/fake/fake_acid.zalan.do_client.go index 9e31f5192..03e7dda94 100644 --- a/pkg/generated/clientset/versioned/typed/acid.zalan.do/v1/fake/fake_acid.zalan.do_client.go +++ b/pkg/generated/clientset/versioned/typed/acid.zalan.do/v1/fake/fake_acid.zalan.do_client.go @@ -1,5 +1,5 @@ /* -Copyright 2020 Compose, Zalando SE +Copyright 2021 Compose, Zalando SE Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/pkg/generated/clientset/versioned/typed/acid.zalan.do/v1/fake/fake_operatorconfiguration.go b/pkg/generated/clientset/versioned/typed/acid.zalan.do/v1/fake/fake_operatorconfiguration.go index d515a0080..c03ea7d94 100644 --- a/pkg/generated/clientset/versioned/typed/acid.zalan.do/v1/fake/fake_operatorconfiguration.go +++ b/pkg/generated/clientset/versioned/typed/acid.zalan.do/v1/fake/fake_operatorconfiguration.go @@ -1,5 +1,5 @@ /* -Copyright 2020 Compose, Zalando SE +Copyright 2021 Compose, Zalando SE Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/pkg/generated/clientset/versioned/typed/acid.zalan.do/v1/fake/fake_postgresql.go b/pkg/generated/clientset/versioned/typed/acid.zalan.do/v1/fake/fake_postgresql.go index e4d72f882..01a0ed7a4 100644 --- a/pkg/generated/clientset/versioned/typed/acid.zalan.do/v1/fake/fake_postgresql.go +++ b/pkg/generated/clientset/versioned/typed/acid.zalan.do/v1/fake/fake_postgresql.go @@ -1,5 +1,5 @@ /* -Copyright 2020 Compose, Zalando SE +Copyright 2021 Compose, Zalando SE Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/pkg/generated/clientset/versioned/typed/acid.zalan.do/v1/fake/fake_postgresteam.go b/pkg/generated/clientset/versioned/typed/acid.zalan.do/v1/fake/fake_postgresteam.go index 20c8ec809..b333ae046 100644 --- a/pkg/generated/clientset/versioned/typed/acid.zalan.do/v1/fake/fake_postgresteam.go +++ b/pkg/generated/clientset/versioned/typed/acid.zalan.do/v1/fake/fake_postgresteam.go @@ -1,5 +1,5 @@ /* -Copyright 2020 Compose, Zalando SE +Copyright 2021 Compose, Zalando SE Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/pkg/generated/clientset/versioned/typed/acid.zalan.do/v1/generated_expansion.go b/pkg/generated/clientset/versioned/typed/acid.zalan.do/v1/generated_expansion.go index bcd80f922..b4e99cbc8 100644 --- a/pkg/generated/clientset/versioned/typed/acid.zalan.do/v1/generated_expansion.go +++ b/pkg/generated/clientset/versioned/typed/acid.zalan.do/v1/generated_expansion.go @@ -1,5 +1,5 @@ /* -Copyright 2020 Compose, Zalando SE +Copyright 2021 Compose, Zalando SE Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/pkg/generated/clientset/versioned/typed/acid.zalan.do/v1/operatorconfiguration.go b/pkg/generated/clientset/versioned/typed/acid.zalan.do/v1/operatorconfiguration.go index 80ef6d6f3..be22e075d 100644 --- a/pkg/generated/clientset/versioned/typed/acid.zalan.do/v1/operatorconfiguration.go +++ b/pkg/generated/clientset/versioned/typed/acid.zalan.do/v1/operatorconfiguration.go @@ -1,5 +1,5 @@ /* -Copyright 2020 Compose, Zalando SE +Copyright 2021 Compose, Zalando SE Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/pkg/generated/clientset/versioned/typed/acid.zalan.do/v1/postgresql.go b/pkg/generated/clientset/versioned/typed/acid.zalan.do/v1/postgresql.go index ca8c6d7ee..5241cfb54 100644 --- a/pkg/generated/clientset/versioned/typed/acid.zalan.do/v1/postgresql.go +++ b/pkg/generated/clientset/versioned/typed/acid.zalan.do/v1/postgresql.go @@ -1,5 +1,5 @@ /* -Copyright 2020 Compose, Zalando SE +Copyright 2021 Compose, Zalando SE Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/pkg/generated/clientset/versioned/typed/acid.zalan.do/v1/postgresteam.go b/pkg/generated/clientset/versioned/typed/acid.zalan.do/v1/postgresteam.go index 82157dceb..96fbb882a 100644 --- a/pkg/generated/clientset/versioned/typed/acid.zalan.do/v1/postgresteam.go +++ b/pkg/generated/clientset/versioned/typed/acid.zalan.do/v1/postgresteam.go @@ -1,5 +1,5 @@ /* -Copyright 2020 Compose, Zalando SE +Copyright 2021 Compose, Zalando SE Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/pkg/generated/informers/externalversions/acid.zalan.do/interface.go b/pkg/generated/informers/externalversions/acid.zalan.do/interface.go index 4ff4a3d06..6f77564fa 100644 --- a/pkg/generated/informers/externalversions/acid.zalan.do/interface.go +++ b/pkg/generated/informers/externalversions/acid.zalan.do/interface.go @@ -1,5 +1,5 @@ /* -Copyright 2020 Compose, Zalando SE +Copyright 2021 Compose, Zalando SE Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/pkg/generated/informers/externalversions/acid.zalan.do/v1/interface.go b/pkg/generated/informers/externalversions/acid.zalan.do/v1/interface.go index b83d4d0f0..5c05e6d68 100644 --- a/pkg/generated/informers/externalversions/acid.zalan.do/v1/interface.go +++ b/pkg/generated/informers/externalversions/acid.zalan.do/v1/interface.go @@ -1,5 +1,5 @@ /* -Copyright 2020 Compose, Zalando SE +Copyright 2021 Compose, Zalando SE Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/pkg/generated/informers/externalversions/acid.zalan.do/v1/postgresql.go b/pkg/generated/informers/externalversions/acid.zalan.do/v1/postgresql.go index be09839d3..1453af276 100644 --- a/pkg/generated/informers/externalversions/acid.zalan.do/v1/postgresql.go +++ b/pkg/generated/informers/externalversions/acid.zalan.do/v1/postgresql.go @@ -1,5 +1,5 @@ /* -Copyright 2020 Compose, Zalando SE +Copyright 2021 Compose, Zalando SE Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/pkg/generated/informers/externalversions/acid.zalan.do/v1/postgresteam.go b/pkg/generated/informers/externalversions/acid.zalan.do/v1/postgresteam.go index 7ae532cbd..a19e4726f 100644 --- a/pkg/generated/informers/externalversions/acid.zalan.do/v1/postgresteam.go +++ b/pkg/generated/informers/externalversions/acid.zalan.do/v1/postgresteam.go @@ -1,5 +1,5 @@ /* -Copyright 2020 Compose, Zalando SE +Copyright 2021 Compose, Zalando SE Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/pkg/generated/informers/externalversions/factory.go b/pkg/generated/informers/externalversions/factory.go index 4e6b36614..e4b1efdc6 100644 --- a/pkg/generated/informers/externalversions/factory.go +++ b/pkg/generated/informers/externalversions/factory.go @@ -1,5 +1,5 @@ /* -Copyright 2020 Compose, Zalando SE +Copyright 2021 Compose, Zalando SE Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/pkg/generated/informers/externalversions/generic.go b/pkg/generated/informers/externalversions/generic.go index 7dff3e4e5..5fd693558 100644 --- a/pkg/generated/informers/externalversions/generic.go +++ b/pkg/generated/informers/externalversions/generic.go @@ -1,5 +1,5 @@ /* -Copyright 2020 Compose, Zalando SE +Copyright 2021 Compose, Zalando SE Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/pkg/generated/informers/externalversions/internalinterfaces/factory_interfaces.go b/pkg/generated/informers/externalversions/internalinterfaces/factory_interfaces.go index 9f4e14a1a..6d1b334bf 100644 --- a/pkg/generated/informers/externalversions/internalinterfaces/factory_interfaces.go +++ b/pkg/generated/informers/externalversions/internalinterfaces/factory_interfaces.go @@ -1,5 +1,5 @@ /* -Copyright 2020 Compose, Zalando SE +Copyright 2021 Compose, Zalando SE Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/pkg/generated/listers/acid.zalan.do/v1/expansion_generated.go b/pkg/generated/listers/acid.zalan.do/v1/expansion_generated.go index 81e829926..cc3e578b2 100644 --- a/pkg/generated/listers/acid.zalan.do/v1/expansion_generated.go +++ b/pkg/generated/listers/acid.zalan.do/v1/expansion_generated.go @@ -1,5 +1,5 @@ /* -Copyright 2020 Compose, Zalando SE +Copyright 2021 Compose, Zalando SE Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/pkg/generated/listers/acid.zalan.do/v1/postgresql.go b/pkg/generated/listers/acid.zalan.do/v1/postgresql.go index ee3efbdfe..d2258bd01 100644 --- a/pkg/generated/listers/acid.zalan.do/v1/postgresql.go +++ b/pkg/generated/listers/acid.zalan.do/v1/postgresql.go @@ -1,5 +1,5 @@ /* -Copyright 2020 Compose, Zalando SE +Copyright 2021 Compose, Zalando SE Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/pkg/generated/listers/acid.zalan.do/v1/postgresteam.go b/pkg/generated/listers/acid.zalan.do/v1/postgresteam.go index 102dae832..38073e92d 100644 --- a/pkg/generated/listers/acid.zalan.do/v1/postgresteam.go +++ b/pkg/generated/listers/acid.zalan.do/v1/postgresteam.go @@ -1,5 +1,5 @@ /* -Copyright 2020 Compose, Zalando SE +Copyright 2021 Compose, Zalando SE Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/ui/Dockerfile b/ui/Dockerfile index 5ea912dbc..9384f90db 100644 --- a/ui/Dockerfile +++ b/ui/Dockerfile @@ -1,5 +1,5 @@ FROM alpine:3.6 -MAINTAINER team-acid@zalando.de +LABEL maintainer="Team ACID @ Zalando " EXPOSE 8081 From f927d6616c186de0aa1789d94a537551049da8d1 Mon Sep 17 00:00:00 2001 From: Felix Kunde Date: Mon, 11 Jan 2021 17:24:24 +0100 Subject: [PATCH 15/35] add default values to operatorconfiguration crd (#1283) * add default values to operatorconfiguration crd * leave default for enable_master_load_balancer to true * add missing bits for new logical backup option * fix wrong lb tag and update chart package --- .../crds/operatorconfigurations.yaml | 110 ++++++++++++++++-- .../postgres-operator-1.6.0.tgz | Bin 18549 -> 19074 bytes charts/postgres-operator/values-crd.yaml | 2 + charts/postgres-operator/values.yaml | 1 + docs/reference/operator_parameters.md | 2 +- manifests/configmap.yaml | 1 + manifests/operatorconfiguration.crd.yaml | 108 +++++++++++++++-- pkg/apis/acid.zalan.do/v1/crds.go | 8 +- pkg/controller/operator_config.go | 6 +- pkg/util/config/config.go | 2 +- pkg/util/util.go | 8 ++ 11 files changed, 218 insertions(+), 30 deletions(-) diff --git a/charts/postgres-operator/crds/operatorconfigurations.yaml b/charts/postgres-operator/crds/operatorconfigurations.yaml index 2ac9ca7fc..09c29002c 100644 --- a/charts/postgres-operator/crds/operatorconfigurations.yaml +++ b/charts/postgres-operator/crds/operatorconfigurations.yaml @@ -65,32 +65,45 @@ spec: properties: docker_image: type: string + default: "registry.opensource.zalan.do/acid/spilo-13:2.0-p2" enable_crd_validation: type: boolean + default: true enable_lazy_spilo_upgrade: type: boolean + default: false enable_pgversion_env_var: type: boolean + default: true enable_shm_volume: type: boolean + default: true enable_spilo_wal_path_compat: type: boolean + default: false etcd_host: type: string + default: "" kubernetes_use_configmaps: type: boolean + default: false max_instances: type: integer minimum: -1 # -1 = disabled + default: -1 min_instances: type: integer minimum: -1 # -1 = disabled + default: -1 resync_period: type: string + default: "30m" repair_period: type: string + default: "5m" set_memory_request_to_limit: type: boolean + default: false sidecar_docker_images: type: object additionalProperties: @@ -104,24 +117,31 @@ spec: workers: type: integer minimum: 1 + default: 8 users: type: object properties: replication_username: type: string + default: standby super_username: type: string + default: postgres kubernetes: type: object properties: cluster_domain: type: string + default: "cluster.local" cluster_labels: type: object additionalProperties: type: string + default: + application: spilo cluster_name_label: type: string + default: "cluster-name" custom_pod_annotations: type: object additionalProperties: @@ -136,12 +156,16 @@ spec: type: string enable_init_containers: type: boolean + default: true enable_pod_antiaffinity: type: boolean + default: false enable_pod_disruption_budget: type: boolean + default: true enable_sidecars: type: boolean + default: true infrastructure_roles_secret_name: type: string infrastructure_roles_secrets: @@ -180,16 +204,20 @@ spec: type: string master_pod_move_timeout: type: string + default: "20m" node_readiness_label: type: object additionalProperties: type: string oauth_token_secret_name: type: string + default: "postgresql-operator" pdb_name_format: type: string + default: "postgres-{cluster}-pdb" pod_antiaffinity_topology_key: type: string + default: "kubernetes.io/hostname" pod_environment_configmap: type: string pod_environment_secret: @@ -199,20 +227,27 @@ spec: enum: - "ordered_ready" - "parallel" + default: "ordered_ready" pod_priority_class_name: type: string pod_role_label: type: string + default: "spilo-role" pod_service_account_definition: type: string + default: "" pod_service_account_name: type: string + default: "postgres-pod" pod_service_account_role_binding_definition: type: string + default: "" pod_terminate_grace_period: type: string + default: "5m" secret_name_template: type: string + default: "{username}.{cluster}.credentials.{tprkind}.{tprgroup}" spilo_runasuser: type: integer spilo_runasgroup: @@ -221,12 +256,14 @@ spec: type: integer spilo_privileged: type: boolean + default: false storage_resize_mode: type: string enum: - "ebs" - "pvc" - "off" + default: "pvc" toleration: type: object additionalProperties: @@ -239,36 +276,48 @@ spec: default_cpu_limit: type: string pattern: '^(\d+m|\d+(\.\d{1,3})?)$' + default: "1" default_cpu_request: type: string pattern: '^(\d+m|\d+(\.\d{1,3})?)$' + default: "100m" default_memory_limit: type: string pattern: '^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$' + default: "500Mi" default_memory_request: type: string pattern: '^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$' + default: "100Mi" min_cpu_limit: type: string pattern: '^(\d+m|\d+(\.\d{1,3})?)$' + default: "250m" min_memory_limit: type: string pattern: '^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$' + default: "250Mi" timeouts: type: object properties: pod_label_wait_timeout: type: string + default: "10m" pod_deletion_wait_timeout: type: string + default: "10m" ready_wait_interval: type: string + default: "4s" ready_wait_timeout: type: string + default: "30s" resource_check_interval: type: string + default: "3s" resource_check_timeout: type: string + default: "10m" load_balancer: type: object properties: @@ -278,19 +327,25 @@ spec: type: string db_hosted_zone: type: string + default: "db.example.com" enable_master_load_balancer: type: boolean + default: true enable_replica_load_balancer: type: boolean + default: false external_traffic_policy: type: string enum: - "Cluster" - "Local" + default: "Cluster" master_dns_name_format: type: string + default: "{cluster}.{team}.{hostedzone}" replica_dns_name_format: type: string + default: "{cluster}-repl.{team}.{hostedzone}" aws_or_gcp: type: object properties: @@ -298,12 +353,16 @@ spec: type: string additional_secret_mount_path: type: string + default: "/meta/credentials" aws_region: type: string + default: "eu-central-1" enable_ebs_gp3_migration: type: boolean + default: false enable_ebs_gp3_migration_max_size: type: integer + default: 1000 gcp_credentials: type: string kube_iam_role: @@ -319,10 +378,15 @@ spec: properties: logical_backup_docker_image: type: string + default: "registry.opensource.zalan.do/acid/logical-backup:v1.6.0" logical_backup_google_application_credentials: type: string + logical_backup_job_prefix: + type: string + default: "logical-backup-" logical_backup_provider: type: string + default: "s3" logical_backup_s3_access_key_id: type: string logical_backup_s3_bucket: @@ -338,30 +402,40 @@ spec: logical_backup_schedule: type: string pattern: '^(\d+|\*)(/\d+)?(\s+(\d+|\*)(/\d+)?){4}$' + default: "30 00 * * *" debug: type: object properties: debug_logging: type: boolean + default: true enable_database_access: type: boolean + default: true teams_api: type: object properties: enable_admin_role_for_users: type: boolean + default: true enable_postgres_team_crd: type: boolean + default: true enable_postgres_team_crd_superusers: type: boolean + default: false enable_team_superuser: type: boolean + default: false enable_teams_api: type: boolean + default: true pam_configuration: type: string + default: "https://info.example.com/oauth2/tokeninfo?access_token= uid realm=/employees" pam_role_name: type: string + default: "zalandos" postgres_superuser_teams: type: array items: @@ -370,23 +444,32 @@ spec: type: array items: type: string + default: + - admin team_admin_role: type: string + default: "admin" team_api_role_configuration: type: object additionalProperties: type: string + default: + log_statement: all teams_api_url: type: string + defaults: "https://teams.example.com/api/" logging_rest_api: type: object properties: api_port: type: integer + default: 8080 cluster_history_entries: type: integer + default: 1000 ring_log_lines: type: integer + default: 100 scalyr: # deprecated type: object properties: @@ -395,60 +478,65 @@ spec: scalyr_cpu_limit: type: string pattern: '^(\d+m|\d+(\.\d{1,3})?)$' + default: "1" scalyr_cpu_request: type: string pattern: '^(\d+m|\d+(\.\d{1,3})?)$' + default: "100m" scalyr_image: type: string scalyr_memory_limit: type: string pattern: '^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$' + default: "500Mi" scalyr_memory_request: type: string pattern: '^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$' + default: "50Mi" scalyr_server_url: type: string + default: "https://upload.eu.scalyr.com" connection_pooler: type: object properties: connection_pooler_schema: type: string - #default: "pooler" + default: "pooler" connection_pooler_user: type: string - #default: "pooler" + default: "pooler" connection_pooler_image: type: string - #default: "registry.opensource.zalan.do/acid/pgbouncer" + default: "registry.opensource.zalan.do/acid/pgbouncer:master-12" connection_pooler_max_db_connections: type: integer - #default: 60 + default: 60 connection_pooler_mode: type: string enum: - "session" - "transaction" - #default: "transaction" + default: "transaction" connection_pooler_number_of_instances: type: integer - minimum: 2 - #default: 2 + minimum: 1 + default: 2 connection_pooler_default_cpu_limit: type: string pattern: '^(\d+m|\d+(\.\d{1,3})?)$' - #default: "1" + default: "1" connection_pooler_default_cpu_request: type: string pattern: '^(\d+m|\d+(\.\d{1,3})?)$' - #default: "500m" + default: "500m" connection_pooler_default_memory_limit: type: string pattern: '^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$' - #default: "100Mi" + default: "100Mi" connection_pooler_default_memory_request: type: string pattern: '^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$' - #default: "100Mi" + default: "100Mi" status: type: object additionalProperties: diff --git a/charts/postgres-operator/postgres-operator-1.6.0.tgz b/charts/postgres-operator/postgres-operator-1.6.0.tgz index b57f99027d13b696e638eb10a52b7ab03e24ca4c..bf98cd8183d93920720fcfca1bfee716dabe6960 100644 GIT binary patch delta 19050 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%0DQRNG4`Iz8o{hbGZpwpdl*gj6| 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 literal 18549 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 diff --git a/charts/postgres-operator/values-crd.yaml b/charts/postgres-operator/values-crd.yaml index 773f311d8..3593dd276 100644 --- a/charts/postgres-operator/values-crd.yaml +++ b/charts/postgres-operator/values-crd.yaml @@ -252,6 +252,8 @@ configLogicalBackup: # path of google cloud service account json file # logical_backup_google_application_credentials: "" + # prefix for the backup job name + logical_backup_job_prefix: "logical-backup-" # storage provider - either "s3" or "gcs" logical_backup_provider: "s3" # S3 Access Key ID diff --git a/charts/postgres-operator/values.yaml b/charts/postgres-operator/values.yaml index ebfd49252..15f13df7e 100644 --- a/charts/postgres-operator/values.yaml +++ b/charts/postgres-operator/values.yaml @@ -242,6 +242,7 @@ configLogicalBackup: logical_backup_docker_image: "registry.opensource.zalan.do/acid/logical-backup:v1.6.0" # path of google cloud service account json file # logical_backup_google_application_credentials: "" + # prefix for the backup job name logical_backup_job_prefix: "logical-backup-" # storage provider - either "s3" or "gcs" diff --git a/docs/reference/operator_parameters.md b/docs/reference/operator_parameters.md index 04d5fe23d..212515dc1 100644 --- a/docs/reference/operator_parameters.md +++ b/docs/reference/operator_parameters.md @@ -556,7 +556,7 @@ grouped under the `logical_backup` key. runs `pg_dumpall` on a replica if possible and uploads compressed results to an S3 bucket under the key `/spilo/pg_cluster_name/cluster_k8s_uuid/logical_backups`. The default image is the same image built with the Zalando-internal CI - pipeline. Default: "registry.opensource.zalan.do/acid/logical-backup" + pipeline. Default: "registry.opensource.zalan.do/acid/logical-backup:v1.6.0" * **logical_backup_google_application_credentials** Specifies the path of the google cloud service account json file. Default is empty. diff --git a/manifests/configmap.yaml b/manifests/configmap.yaml index 848389d63..3788d8b32 100644 --- a/manifests/configmap.yaml +++ b/manifests/configmap.yaml @@ -64,6 +64,7 @@ data: # log_s3_bucket: "" logical_backup_docker_image: "registry.opensource.zalan.do/acid/logical-backup:v1.6.0" # logical_backup_google_application_credentials: "" + logical_backup_job_prefix: "logical-backup-" logical_backup_provider: "s3" # logical_backup_s3_access_key_id: "" logical_backup_s3_bucket: "my-bucket-url" diff --git a/manifests/operatorconfiguration.crd.yaml b/manifests/operatorconfiguration.crd.yaml index d52608c15..cc8dbb6cc 100644 --- a/manifests/operatorconfiguration.crd.yaml +++ b/manifests/operatorconfiguration.crd.yaml @@ -61,32 +61,45 @@ spec: properties: docker_image: type: string + default: "registry.opensource.zalan.do/acid/spilo-13:2.0-p2" enable_crd_validation: type: boolean + default: true enable_lazy_spilo_upgrade: type: boolean + default: false enable_pgversion_env_var: type: boolean + default: true enable_shm_volume: type: boolean + default: true enable_spilo_wal_path_compat: type: boolean + default: false etcd_host: type: string + default: "" kubernetes_use_configmaps: type: boolean + default: false max_instances: type: integer minimum: -1 # -1 = disabled + default: -1 min_instances: type: integer minimum: -1 # -1 = disabled + default: -1 resync_period: type: string + default: "30m" repair_period: type: string + default: "5m" set_memory_request_to_limit: type: boolean + default: false sidecar_docker_images: type: object additionalProperties: @@ -100,24 +113,31 @@ spec: workers: type: integer minimum: 1 + default: 8 users: type: object properties: replication_username: type: string + default: standby super_username: type: string + default: postgres kubernetes: type: object properties: cluster_domain: type: string + default: "cluster.local" cluster_labels: type: object additionalProperties: type: string + default: + application: spilo cluster_name_label: type: string + default: "cluster-name" custom_pod_annotations: type: object additionalProperties: @@ -132,12 +152,16 @@ spec: type: string enable_init_containers: type: boolean + default: true enable_pod_antiaffinity: type: boolean + default: false enable_pod_disruption_budget: type: boolean + default: true enable_sidecars: type: boolean + default: true infrastructure_roles_secret_name: type: string infrastructure_roles_secrets: @@ -176,16 +200,20 @@ spec: type: string master_pod_move_timeout: type: string + default: "20m" node_readiness_label: type: object additionalProperties: type: string oauth_token_secret_name: type: string + default: "postgresql-operator" pdb_name_format: type: string + default: "postgres-{cluster}-pdb" pod_antiaffinity_topology_key: type: string + default: "kubernetes.io/hostname" pod_environment_configmap: type: string pod_environment_secret: @@ -195,20 +223,27 @@ spec: enum: - "ordered_ready" - "parallel" + default: "ordered_ready" pod_priority_class_name: type: string pod_role_label: type: string + default: "spilo-role" pod_service_account_definition: type: string + default: "" pod_service_account_name: type: string + default: "postgres-pod" pod_service_account_role_binding_definition: type: string + default: "" pod_terminate_grace_period: type: string + default: "5m" secret_name_template: type: string + default: "{username}.{cluster}.credentials.{tprkind}.{tprgroup}" spilo_runasuser: type: integer spilo_runasgroup: @@ -217,12 +252,14 @@ spec: type: integer spilo_privileged: type: boolean + default: false storage_resize_mode: type: string enum: - "ebs" - "pvc" - "off" + default: "pvc" toleration: type: object additionalProperties: @@ -235,36 +272,48 @@ spec: default_cpu_limit: type: string pattern: '^(\d+m|\d+(\.\d{1,3})?)$' + default: "1" default_cpu_request: type: string pattern: '^(\d+m|\d+(\.\d{1,3})?)$' + default: "100m" default_memory_limit: type: string pattern: '^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$' + default: "500Mi" default_memory_request: type: string pattern: '^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$' + default: "100Mi" min_cpu_limit: type: string pattern: '^(\d+m|\d+(\.\d{1,3})?)$' + default: "250m" min_memory_limit: type: string pattern: '^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$' + default: "250Mi" timeouts: type: object properties: pod_label_wait_timeout: type: string + default: "10m" pod_deletion_wait_timeout: type: string + default: "10m" ready_wait_interval: type: string + default: "4s" ready_wait_timeout: type: string + default: "30s" resource_check_interval: type: string + default: "3s" resource_check_timeout: type: string + default: "10m" load_balancer: type: object properties: @@ -274,19 +323,25 @@ spec: type: string db_hosted_zone: type: string + default: "db.example.com" enable_master_load_balancer: type: boolean + default: true enable_replica_load_balancer: type: boolean + default: false external_traffic_policy: type: string enum: - "Cluster" - "Local" + default: "Cluster" master_dns_name_format: type: string + default: "{cluster}.{team}.{hostedzone}" replica_dns_name_format: type: string + default: "{cluster}-repl.{team}.{hostedzone}" aws_or_gcp: type: object properties: @@ -294,12 +349,16 @@ spec: type: string additional_secret_mount_path: type: string + default: "/meta/credentials" aws_region: type: string + default: "eu-central-1" enable_ebs_gp3_migration: type: boolean + default: false enable_ebs_gp3_migration_max_size: type: integer + default: 1000 gcp_credentials: type: string kube_iam_role: @@ -315,12 +374,15 @@ spec: properties: logical_backup_docker_image: type: string + default: "registry.opensource.zalan.do/acid/logical-backup:v1.6.0" logical_backup_google_application_credentials: type: string logical_backup_job_prefix: type: string + default: "logical-backup-" logical_backup_provider: type: string + default: "s3" logical_backup_s3_access_key_id: type: string logical_backup_s3_bucket: @@ -336,30 +398,40 @@ spec: logical_backup_schedule: type: string pattern: '^(\d+|\*)(/\d+)?(\s+(\d+|\*)(/\d+)?){4}$' + default: "30 00 * * *" debug: type: object properties: debug_logging: type: boolean + default: true enable_database_access: type: boolean + default: true teams_api: type: object properties: enable_admin_role_for_users: type: boolean + default: true enable_postgres_team_crd: type: boolean + default: true enable_postgres_team_crd_superusers: type: boolean + default: false enable_team_superuser: type: boolean + default: false enable_teams_api: type: boolean + default: true pam_configuration: type: string + default: "https://info.example.com/oauth2/tokeninfo?access_token= uid realm=/employees" pam_role_name: type: string + default: "zalandos" postgres_superuser_teams: type: array items: @@ -368,23 +440,32 @@ spec: type: array items: type: string + default: + - admin team_admin_role: type: string + default: "admin" team_api_role_configuration: type: object additionalProperties: type: string + default: + log_statement: all teams_api_url: type: string + defaults: "https://teams.example.com/api/" logging_rest_api: type: object properties: api_port: type: integer + default: 8080 cluster_history_entries: type: integer + default: 1000 ring_log_lines: type: integer + default: 100 scalyr: # deprecated type: object properties: @@ -393,60 +474,65 @@ spec: scalyr_cpu_limit: type: string pattern: '^(\d+m|\d+(\.\d{1,3})?)$' + default: "1" scalyr_cpu_request: type: string pattern: '^(\d+m|\d+(\.\d{1,3})?)$' + default: "100m" scalyr_image: type: string scalyr_memory_limit: type: string pattern: '^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$' + default: "500Mi" scalyr_memory_request: type: string pattern: '^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$' + default: "50Mi" scalyr_server_url: type: string + default: "https://upload.eu.scalyr.com" connection_pooler: type: object properties: connection_pooler_schema: type: string - #default: "pooler" + default: "pooler" connection_pooler_user: type: string - #default: "pooler" + default: "pooler" connection_pooler_image: type: string - #default: "registry.opensource.zalan.do/acid/pgbouncer" + default: "registry.opensource.zalan.do/acid/pgbouncer:master-12" connection_pooler_max_db_connections: type: integer - #default: 60 + default: 60 connection_pooler_mode: type: string enum: - "session" - "transaction" - #default: "transaction" + default: "transaction" connection_pooler_number_of_instances: type: integer - minimum: 2 - #default: 2 + minimum: 1 + default: 2 connection_pooler_default_cpu_limit: type: string pattern: '^(\d+m|\d+(\.\d{1,3})?)$' - #default: "1" + default: "1" connection_pooler_default_cpu_request: type: string pattern: '^(\d+m|\d+(\.\d{1,3})?)$' - #default: "500m" + default: "500m" connection_pooler_default_memory_limit: type: string pattern: '^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$' - #default: "100Mi" + default: "100Mi" connection_pooler_default_memory_request: type: string pattern: '^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$' - #default: "100Mi" + default: "100Mi" status: type: object additionalProperties: diff --git a/pkg/apis/acid.zalan.do/v1/crds.go b/pkg/apis/acid.zalan.do/v1/crds.go index 3d4ff09bc..f03b4c2ab 100644 --- a/pkg/apis/acid.zalan.do/v1/crds.go +++ b/pkg/apis/acid.zalan.do/v1/crds.go @@ -106,7 +106,6 @@ var OperatorConfigCRDResourceColumns = []apiextv1.CustomResourceColumnDefinition var min0 = 0.0 var min1 = 1.0 -var min2 = 2.0 var minDisable = -1.0 // PostgresCRDResourceValidation to check applied manifest parameters @@ -232,7 +231,7 @@ var PostgresCRDResourceValidation = apiextv1.CustomResourceValidation{ }, "numberOfInstances": { Type: "integer", - Minimum: &min2, + Minimum: &min1, }, "resources": { Type: "object", @@ -1294,6 +1293,9 @@ var OperatorConfigCRDResourceValidation = apiextv1.CustomResourceValidation{ "logical_backup_google_application_credentials": { Type: "string", }, + "logical_backup_job_prefix": { + Type: "string", + }, "logical_backup_provider": { Type: "string", }, @@ -1470,7 +1472,7 @@ var OperatorConfigCRDResourceValidation = apiextv1.CustomResourceValidation{ }, "connection_pooler_number_of_instances": { Type: "integer", - Minimum: &min2, + Minimum: &min1, }, "connection_pooler_schema": { Type: "string", diff --git a/pkg/controller/operator_config.go b/pkg/controller/operator_config.go index faf1a4908..16fb05004 100644 --- a/pkg/controller/operator_config.go +++ b/pkg/controller/operator_config.go @@ -141,11 +141,11 @@ func (c *Controller) importConfigurationFromCRD(fromCRD *acidv1.OperatorConfigur result.AdditionalSecretMount = fromCRD.AWSGCP.AdditionalSecretMount result.AdditionalSecretMountPath = util.Coalesce(fromCRD.AWSGCP.AdditionalSecretMountPath, "/meta/credentials") result.EnableEBSGp3Migration = fromCRD.AWSGCP.EnableEBSGp3Migration - result.EnableEBSGp3MigrationMaxSize = fromCRD.AWSGCP.EnableEBSGp3MigrationMaxSize + result.EnableEBSGp3MigrationMaxSize = util.CoalesceInt64(fromCRD.AWSGCP.EnableEBSGp3MigrationMaxSize, 1000) // logical backup config result.LogicalBackupSchedule = util.Coalesce(fromCRD.LogicalBackup.Schedule, "30 00 * * *") - result.LogicalBackupDockerImage = util.Coalesce(fromCRD.LogicalBackup.DockerImage, "registry.opensource.zalan.do/acid/logical-backup") + result.LogicalBackupDockerImage = util.Coalesce(fromCRD.LogicalBackup.DockerImage, "registry.opensource.zalan.do/acid/logical-backup:v1.6.0") result.LogicalBackupProvider = util.Coalesce(fromCRD.LogicalBackup.BackupProvider, "s3") result.LogicalBackupS3Bucket = fromCRD.LogicalBackup.S3Bucket result.LogicalBackupS3Region = fromCRD.LogicalBackup.S3Region @@ -154,7 +154,7 @@ func (c *Controller) importConfigurationFromCRD(fromCRD *acidv1.OperatorConfigur result.LogicalBackupS3SecretAccessKey = fromCRD.LogicalBackup.S3SecretAccessKey result.LogicalBackupS3SSE = fromCRD.LogicalBackup.S3SSE result.LogicalBackupGoogleApplicationCredentials = fromCRD.LogicalBackup.GoogleApplicationCredentials - result.LogicalBackupJobPrefix = fromCRD.LogicalBackup.JobPrefix + result.LogicalBackupJobPrefix = util.Coalesce(fromCRD.LogicalBackup.JobPrefix, "logical-backup-") // debug config result.DebugLogging = fromCRD.OperatorDebug.DebugLogging diff --git a/pkg/util/config/config.go b/pkg/util/config/config.go index 142aa2be9..1d8e37bd2 100644 --- a/pkg/util/config/config.go +++ b/pkg/util/config/config.go @@ -112,7 +112,7 @@ type Scalyr struct { // LogicalBackup defines configuration for logical backup type LogicalBackup struct { LogicalBackupSchedule string `name:"logical_backup_schedule" default:"30 00 * * *"` - LogicalBackupDockerImage string `name:"logical_backup_docker_image" default:"registry.opensource.zalan.do/acid/logical-backup"` + LogicalBackupDockerImage string `name:"logical_backup_docker_image" default:"registry.opensource.zalan.do/acid/logical-backup:v1.6.0"` LogicalBackupProvider string `name:"logical_backup_provider" default:"s3"` LogicalBackupS3Bucket string `name:"logical_backup_s3_bucket" default:""` LogicalBackupS3Region string `name:"logical_backup_s3_region" default:""` diff --git a/pkg/util/util.go b/pkg/util/util.go index 20e2915ba..bebb9f8da 100644 --- a/pkg/util/util.go +++ b/pkg/util/util.go @@ -271,6 +271,14 @@ func CoalesceUInt32(val, defaultVal uint32) uint32 { return val } +// CoalesceInt64 works like coalesce but for int64 +func CoalesceInt64(val, defaultVal int64) int64 { + if val == 0 { + return defaultVal + } + return val +} + // CoalesceBool works like coalesce but for *bool func CoalesceBool(val, defaultVal *bool) *bool { if val == nil { From 010865f5d9fb901f5bf6fac29d856cf9cfac79d0 Mon Sep 17 00:00:00 2001 From: dervoeti Date: Tue, 12 Jan 2021 15:39:54 +0100 Subject: [PATCH 16/35] Fix typo in operatorconfigurations CRD (#1305) * fix typo in operatorconfigurations crd * fix typo in operatorconfigurations manifest --- charts/postgres-operator/crds/operatorconfigurations.yaml | 2 +- manifests/operatorconfiguration.crd.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/charts/postgres-operator/crds/operatorconfigurations.yaml b/charts/postgres-operator/crds/operatorconfigurations.yaml index 09c29002c..a360da0c6 100644 --- a/charts/postgres-operator/crds/operatorconfigurations.yaml +++ b/charts/postgres-operator/crds/operatorconfigurations.yaml @@ -457,7 +457,7 @@ spec: log_statement: all teams_api_url: type: string - defaults: "https://teams.example.com/api/" + default: "https://teams.example.com/api/" logging_rest_api: type: object properties: diff --git a/manifests/operatorconfiguration.crd.yaml b/manifests/operatorconfiguration.crd.yaml index cc8dbb6cc..7add1b8c6 100644 --- a/manifests/operatorconfiguration.crd.yaml +++ b/manifests/operatorconfiguration.crd.yaml @@ -453,7 +453,7 @@ spec: log_statement: all teams_api_url: type: string - defaults: "https://teams.example.com/api/" + default: "https://teams.example.com/api/" logging_rest_api: type: object properties: From ff46bb069bbe9efe7bb9956f2953e64c0ab5f02b Mon Sep 17 00:00:00 2001 From: Felix Kunde Date: Wed, 13 Jan 2021 10:40:55 +0100 Subject: [PATCH 17/35] update docker base images and UI dependencies (#1302) * update docker base images and UI dependencies * use latest compliant base image --- docker/DebugDockerfile | 2 +- docker/Dockerfile | 2 +- docker/logical-backup/Dockerfile | 2 +- ui/Dockerfile | 2 +- ui/requirements.txt | 16 ++++++++-------- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/docker/DebugDockerfile b/docker/DebugDockerfile index 0bfe9a277..e8f51badd 100644 --- a/docker/DebugDockerfile +++ b/docker/DebugDockerfile @@ -1,4 +1,4 @@ -FROM alpine +FROM registry.opensource.zalan.do/library/alpine-3.12:latest LABEL maintainer="Team ACID @ Zalando " # We need root certificates to deal with teams api over https diff --git a/docker/Dockerfile b/docker/Dockerfile index bf844850f..c1b87caf7 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,4 +1,4 @@ -FROM alpine +FROM registry.opensource.zalan.do/library/alpine-3.12:latest LABEL maintainer="Team ACID @ Zalando " # We need root certificates to deal with teams api over https diff --git a/docker/logical-backup/Dockerfile b/docker/logical-backup/Dockerfile index c8bbe6f28..b84ea2b22 100644 --- a/docker/logical-backup/Dockerfile +++ b/docker/logical-backup/Dockerfile @@ -1,4 +1,4 @@ -FROM ubuntu:18.04 +FROM registry.opensource.zalan.do/library/ubuntu-18.04:latest LABEL maintainer="Team ACID @ Zalando " SHELL ["/bin/bash", "-o", "pipefail", "-c"] diff --git a/ui/Dockerfile b/ui/Dockerfile index 9384f90db..ad775ece2 100644 --- a/ui/Dockerfile +++ b/ui/Dockerfile @@ -1,4 +1,4 @@ -FROM alpine:3.6 +FROM registry.opensource.zalan.do/library/alpine-3.12:latest LABEL maintainer="Team ACID @ Zalando " EXPOSE 8081 diff --git a/ui/requirements.txt b/ui/requirements.txt index 7dc49eb3d..8f612d554 100644 --- a/ui/requirements.txt +++ b/ui/requirements.txt @@ -1,15 +1,15 @@ Flask-OAuthlib==0.9.5 Flask==1.1.2 -backoff==1.8.1 -boto3==1.10.4 +backoff==1.10.0 +boto3==1.16.52 boto==2.49.0 -click==6.7 -furl==1.0.2 -gevent==1.2.2 -jq==0.1.6 +click==7.1.2 +furl==2.1.0 +gevent==20.12.1 +jq==1.1.1 json_delta>=2.0 kubernetes==3.0.0 -requests==2.22.0 +requests==2.25.1 stups-tokens>=1.1.19 -wal_e==1.1.0 +wal_e==1.1.1 werkzeug==0.16.1 From e398cf8c7e7553d00a796cb278e2d22542ba2345 Mon Sep 17 00:00:00 2001 From: Rafia Sabih Date: Thu, 14 Jan 2021 09:53:09 +0100 Subject: [PATCH 18/35] Avoid syncing when possible (#1274) Avoid extra syncing in case there are no changes in pooler requirements. Add pooler specific labels to pooler secrets. Add test case to check for pooler secret creation and deletion. Co-authored-by: Rafia Sabih --- e2e/README.md | 2 +- e2e/tests/test_e2e.py | 12 ++++++++-- pkg/cluster/connection_pooler.go | 40 ++++++++++++++++++++++++++++---- pkg/cluster/k8sres.go | 9 ++++++- 4 files changed, 55 insertions(+), 8 deletions(-) diff --git a/e2e/README.md b/e2e/README.md index 3bba6ccc3..5aa987593 100644 --- a/e2e/README.md +++ b/e2e/README.md @@ -44,7 +44,7 @@ To run the end 2 end test and keep the kind state execute: NOCLEANUP=True ./run.sh main ``` -## Run indidual test +## Run individual test After having executed a normal E2E run with `NOCLEANUP=True` Kind still continues to run, allowing you subsequent test runs. diff --git a/e2e/tests/test_e2e.py b/e2e/tests/test_e2e.py index 9e2035652..ecc0b2327 100644 --- a/e2e/tests/test_e2e.py +++ b/e2e/tests/test_e2e.py @@ -160,7 +160,7 @@ class EndToEndTestCase(unittest.TestCase): self.k8s.create_with_kubectl("manifests/minimal-fake-pooler-deployment.yaml") self.eventuallyEqual(lambda: self.k8s.get_operator_state(), {"0": "idle"}, "Operator does not get in sync") self.eventuallyEqual(lambda: self.k8s.get_deployment_replica_count(name="acid-minimal-cluster-pooler"), 1, - "Initial broken deplyment not rolled out") + "Initial broken deployment not rolled out") self.k8s.api.custom_objects_api.patch_namespaced_custom_object( 'acid.zalan.do', 'v1', 'default', @@ -221,6 +221,8 @@ class EndToEndTestCase(unittest.TestCase): self.eventuallyEqual(lambda: k8s.count_services_with_label( 'application=db-connection-pooler,cluster-name=acid-minimal-cluster'), 2, "No pooler service found") + self.eventuallyEqual(lambda: k8s.count_secrets_with_label('application=db-connection-pooler,cluster-name=acid-minimal-cluster'), + 1, "Pooler secret not created") # Turn off only master connection pooler k8s.api.custom_objects_api.patch_namespaced_custom_object( @@ -246,6 +248,8 @@ class EndToEndTestCase(unittest.TestCase): self.eventuallyEqual(lambda: k8s.count_services_with_label( 'application=db-connection-pooler,cluster-name=acid-minimal-cluster'), 1, "No pooler service found") + self.eventuallyEqual(lambda: k8s.count_secrets_with_label('application=db-connection-pooler,cluster-name=acid-minimal-cluster'), + 1, "Secret not created") # Turn off only replica connection pooler k8s.api.custom_objects_api.patch_namespaced_custom_object( @@ -268,6 +272,8 @@ class EndToEndTestCase(unittest.TestCase): 0, "Pooler replica pods not deleted") self.eventuallyEqual(lambda: k8s.count_services_with_label('application=db-connection-pooler,cluster-name=acid-minimal-cluster'), 1, "No pooler service found") + self.eventuallyEqual(lambda: k8s.count_secrets_with_label('application=db-connection-pooler,cluster-name=acid-minimal-cluster'), + 1, "Secret not created") # scale up connection pooler deployment k8s.api.custom_objects_api.patch_namespaced_custom_object( @@ -301,6 +307,8 @@ class EndToEndTestCase(unittest.TestCase): 0, "Pooler pods not scaled down") self.eventuallyEqual(lambda: k8s.count_services_with_label('application=db-connection-pooler,cluster-name=acid-minimal-cluster'), 0, "Pooler service not removed") + self.eventuallyEqual(lambda: k8s.count_secrets_with_label('application=spilo,cluster-name=acid-minimal-cluster'), + 4, "Secrets not deleted") # Verify that all the databases have pooler schema installed. # Do this via psql, since otherwise we need to deal with @@ -1034,7 +1042,7 @@ class EndToEndTestCase(unittest.TestCase): except timeout_decorator.TimeoutError: print('Operator log: {}'.format(k8s.get_operator_log())) raise - + @timeout_decorator.timeout(TEST_TIMEOUT_SEC) def test_zzzz_cluster_deletion(self): ''' diff --git a/pkg/cluster/connection_pooler.go b/pkg/cluster/connection_pooler.go index 1d1d609e4..e6cc60cb2 100644 --- a/pkg/cluster/connection_pooler.go +++ b/pkg/cluster/connection_pooler.go @@ -539,13 +539,13 @@ func updateConnectionPoolerAnnotations(KubeClient k8sutil.KubernetesClient, depl // Test if two connection pooler configuration needs to be synced. For simplicity // compare not the actual K8S objects, but the configuration itself and request // sync if there is any difference. -func needSyncConnectionPoolerSpecs(oldSpec, newSpec *acidv1.ConnectionPooler) (sync bool, reasons []string) { +func needSyncConnectionPoolerSpecs(oldSpec, newSpec *acidv1.ConnectionPooler, logger *logrus.Entry) (sync bool, reasons []string) { reasons = []string{} sync = false changelog, err := diff.Diff(oldSpec, newSpec) if err != nil { - //c.logger.Infof("Cannot get diff, do not do anything, %+v", err) + logger.Infof("cannot get diff, do not do anything, %+v", err) return false, reasons } @@ -681,13 +681,45 @@ func logPoolerEssentials(log *logrus.Entry, oldSpec, newSpec *acidv1.Postgresql) } func (c *Cluster) syncConnectionPooler(oldSpec, newSpec *acidv1.Postgresql, LookupFunction InstallFunction) (SyncReason, error) { - logPoolerEssentials(c.logger, oldSpec, newSpec) var reason SyncReason var err error var newNeedConnectionPooler, oldNeedConnectionPooler bool oldNeedConnectionPooler = false + if oldSpec == nil { + oldSpec = &acidv1.Postgresql{ + Spec: acidv1.PostgresSpec{ + ConnectionPooler: &acidv1.ConnectionPooler{}, + }, + } + } + + needSync, _ := needSyncConnectionPoolerSpecs(oldSpec.Spec.ConnectionPooler, newSpec.Spec.ConnectionPooler, c.logger) + masterChanges, err := diff.Diff(oldSpec.Spec.EnableConnectionPooler, newSpec.Spec.EnableConnectionPooler) + if err != nil { + c.logger.Error("Error in getting diff of master connection pooler changes") + } + replicaChanges, err := diff.Diff(oldSpec.Spec.EnableReplicaConnectionPooler, newSpec.Spec.EnableReplicaConnectionPooler) + if err != nil { + c.logger.Error("Error in getting diff of replica connection pooler changes") + } + + // skip pooler sync only + // 1. if there is no diff in spec, AND + // 2. if connection pooler is already there and is also required as per newSpec + // + // Handling the case when connectionPooler is not there but it is required + // as per spec, hence do not skip syncing in that case, even though there + // is no diff in specs + if (!needSync && len(masterChanges) <= 0 && len(replicaChanges) <= 0) && + (c.ConnectionPooler != nil && *newSpec.Spec.EnableConnectionPooler) { + c.logger.Debugln("syncing pooler is not required") + return nil, nil + } + + logPoolerEssentials(c.logger, oldSpec, newSpec) + // Check and perform the sync requirements for each of the roles. for _, role := range [2]PostgresRole{Master, Replica} { @@ -841,7 +873,7 @@ func (c *Cluster) syncConnectionPoolerWorker(oldSpec, newSpec *acidv1.Postgresql var specReason []string if oldSpec != nil { - specSync, specReason = needSyncConnectionPoolerSpecs(oldConnectionPooler, newConnectionPooler) + specSync, specReason = needSyncConnectionPoolerSpecs(oldConnectionPooler, newConnectionPooler, c.logger) } defaultsSync, defaultsReason := needSyncConnectionPoolerDefaults(&c.Config, newConnectionPooler, deployment) diff --git a/pkg/cluster/k8sres.go b/pkg/cluster/k8sres.go index 6b1af045f..06b074b4c 100644 --- a/pkg/cluster/k8sres.go +++ b/pkg/cluster/k8sres.go @@ -1561,11 +1561,17 @@ func (c *Cluster) generateSingleUserSecret(namespace string, pgUser spec.PgUser) } username := pgUser.Name + lbls := c.labelsSet(true) + + if username == constants.ConnectionPoolerUserName { + lbls = c.connectionPoolerLabels("", false).MatchLabels + } + secret := v1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: c.credentialSecretName(username), Namespace: namespace, - Labels: c.labelsSet(true), + Labels: lbls, Annotations: c.annotationsSet(nil), }, Type: v1.SecretTypeOpaque, @@ -1574,6 +1580,7 @@ func (c *Cluster) generateSingleUserSecret(namespace string, pgUser spec.PgUser) "password": []byte(pgUser.Password), }, } + return &secret } From 258799b420e4c2193b8fa3afd6f3f9113fa952a1 Mon Sep 17 00:00:00 2001 From: Felix Kunde Date: Fri, 15 Jan 2021 15:11:02 +0100 Subject: [PATCH 19/35] allow additional members from other teams (#1314) --- pkg/cluster/util.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/cluster/util.go b/pkg/cluster/util.go index d5e887656..393a490bd 100644 --- a/pkg/cluster/util.go +++ b/pkg/cluster/util.go @@ -240,7 +240,7 @@ func (c *Cluster) getTeamMembers(teamID string) ([]string, error) { c.logger.Debugf("fetching possible additional team members for team %q", teamID) members := []string{} - additionalMembers := c.PgTeamMap[c.Spec.TeamID].AdditionalMembers + additionalMembers := c.PgTeamMap[teamID].AdditionalMembers for _, member := range additionalMembers { members = append(members, member) } From 2b45478f3ac00e74036b7a43e5de3ab83aff0c23 Mon Sep 17 00:00:00 2001 From: Felix Kunde Date: Tue, 19 Jan 2021 10:47:32 +0100 Subject: [PATCH 20/35] add host info to connection docs (#1319) --- docs/user.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/user.md b/docs/user.md index 3463983f7..ec5941d9e 100644 --- a/docs/user.md +++ b/docs/user.md @@ -71,26 +71,26 @@ kubectl describe postgresql acid-minimal-cluster ## Connect to PostgreSQL With a `port-forward` on one of the database pods (e.g. the master) you can -connect to the PostgreSQL database. Use labels to filter for the master pod of -our test cluster. +connect to the PostgreSQL database from your machine. Use labels to filter for +the master pod of our test cluster. ```bash # get name of master pod of acid-minimal-cluster -export PGMASTER=$(kubectl get pods -o jsonpath={.items..metadata.name} -l application=spilo,cluster-name=acid-minimal-cluster,spilo-role=master) +export PGMASTER=$(kubectl get pods -o jsonpath={.items..metadata.name} -l application=spilo,cluster-name=acid-minimal-cluster,spilo-role=master -n default) # set up port forward -kubectl port-forward $PGMASTER 6432:5432 +kubectl port-forward $PGMASTER 6432:5432 -n default ``` -Open another CLI and connect to the database. Use the generated secret of the -`postgres` robot user to connect to our `acid-minimal-cluster` master running -in Minikube. As non-encrypted connections are rejected by default set the SSL -mode to require: +Open another CLI and connect to the database using e.g. the psql client. +When connecting with the `postgres` user read its password from the K8s secret +which was generated when creating the `acid-minimal-cluster`. As non-encrypted +connections are rejected by default set the SSL mode to `require`: ```bash export PGPASSWORD=$(kubectl get secret postgres.acid-minimal-cluster.credentials -o 'jsonpath={.data.password}' | base64 -d) export PGSSLMODE=require -psql -U postgres -p 6432 +psql -U postgres -h localhost -p 6432 ``` ## Defining database roles in the operator From a9b677c957be09de5e02f3b0eec4221c551eb2ca Mon Sep 17 00:00:00 2001 From: Rafia Sabih Date: Tue, 19 Jan 2021 17:40:20 +0100 Subject: [PATCH 21/35] Use fake client for connection pooler (#1301) Connection pooler creation, deletion, and synchronization now tested using fake client API. Co-authored-by: Rafia Sabih --- pkg/cluster/connection_pooler.go | 2 +- pkg/cluster/connection_pooler_test.go | 540 ++++++++++++++------------ 2 files changed, 297 insertions(+), 245 deletions(-) diff --git a/pkg/cluster/connection_pooler.go b/pkg/cluster/connection_pooler.go index e6cc60cb2..2e3f04876 100644 --- a/pkg/cluster/connection_pooler.go +++ b/pkg/cluster/connection_pooler.go @@ -713,7 +713,7 @@ func (c *Cluster) syncConnectionPooler(oldSpec, newSpec *acidv1.Postgresql, Look // as per spec, hence do not skip syncing in that case, even though there // is no diff in specs if (!needSync && len(masterChanges) <= 0 && len(replicaChanges) <= 0) && - (c.ConnectionPooler != nil && *newSpec.Spec.EnableConnectionPooler) { + (c.ConnectionPooler != nil && (needConnectionPooler(&newSpec.Spec))) { c.logger.Debugln("syncing pooler is not required") return nil, nil } diff --git a/pkg/cluster/connection_pooler_test.go b/pkg/cluster/connection_pooler_test.go index 54be0f5bd..280adb101 100644 --- a/pkg/cluster/connection_pooler_test.go +++ b/pkg/cluster/connection_pooler_test.go @@ -6,13 +6,17 @@ import ( "strings" "testing" + "github.com/stretchr/testify/assert" acidv1 "github.com/zalando/postgres-operator/pkg/apis/acid.zalan.do/v1" + fakeacidv1 "github.com/zalando/postgres-operator/pkg/generated/clientset/versioned/fake" + "github.com/zalando/postgres-operator/pkg/util" "github.com/zalando/postgres-operator/pkg/util/config" "github.com/zalando/postgres-operator/pkg/util/k8sutil" appsv1 "k8s.io/api/apps/v1" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes/fake" ) func mockInstallLookupFunction(schema string, user string, role PostgresRole) error { @@ -27,79 +31,122 @@ func int32ToPointer(value int32) *int32 { return &value } -func TestConnectionPoolerCreationAndDeletion(t *testing.T) { - testName := "Test connection pooler creation" - var cluster = New( - Config{ - OpConfig: config.Config{ - ProtectedRoles: []string{"admin"}, - Auth: config.Auth{ - SuperUsername: superUserName, - ReplicationUsername: replicationUserName, - }, - ConnectionPooler: config.ConnectionPooler{ - ConnectionPoolerDefaultCPURequest: "100m", - ConnectionPoolerDefaultCPULimit: "100m", - ConnectionPoolerDefaultMemoryRequest: "100Mi", - ConnectionPoolerDefaultMemoryLimit: "100Mi", - NumberOfInstances: int32ToPointer(1), - }, - }, - }, k8sutil.NewMockKubernetesClient(), acidv1.Postgresql{}, logger, eventRecorder) - - cluster.Statefulset = &appsv1.StatefulSet{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-sts", - }, - } - - cluster.Spec = acidv1.PostgresSpec{ - ConnectionPooler: &acidv1.ConnectionPooler{}, - EnableReplicaConnectionPooler: boolToPointer(true), - } - - reason, err := cluster.createConnectionPooler(mockInstallLookupFunction) - - if err != nil { - t.Errorf("%s: Cannot create connection pooler, %s, %+v", - testName, err, reason) - } +func deploymentUpdated(cluster *Cluster, err error, reason SyncReason) error { for _, role := range [2]PostgresRole{Master, Replica} { - if cluster.ConnectionPooler[role] != nil { - if cluster.ConnectionPooler[role].Deployment == nil { - t.Errorf("%s: Connection pooler deployment is empty for role %s", testName, role) - } - if cluster.ConnectionPooler[role].Service == nil { - t.Errorf("%s: Connection pooler service is empty for role %s", testName, role) - } + poolerLabels := cluster.labelsSet(false) + poolerLabels["application"] = "db-connection-pooler" + poolerLabels["connection-pooler"] = cluster.connectionPoolerName(role) + + if cluster.ConnectionPooler[role] != nil && cluster.ConnectionPooler[role].Deployment != nil && + util.MapContains(cluster.ConnectionPooler[role].Deployment.Labels, poolerLabels) && + (cluster.ConnectionPooler[role].Deployment.Spec.Replicas == nil || + *cluster.ConnectionPooler[role].Deployment.Spec.Replicas != 2) { + return fmt.Errorf("Wrong number of instances") } } - oldSpec := &acidv1.Postgresql{ - Spec: acidv1.PostgresSpec{ - EnableConnectionPooler: boolToPointer(true), - EnableReplicaConnectionPooler: boolToPointer(true), - }, - } - newSpec := &acidv1.Postgresql{ - Spec: acidv1.PostgresSpec{ - EnableConnectionPooler: boolToPointer(false), - EnableReplicaConnectionPooler: boolToPointer(false), - }, + return nil +} + +func objectsAreSaved(cluster *Cluster, err error, reason SyncReason) error { + if cluster.ConnectionPooler == nil { + return fmt.Errorf("Connection pooler resources are empty") } - // Delete connection pooler via sync - _, err = cluster.syncConnectionPooler(oldSpec, newSpec, mockInstallLookupFunction) - if err != nil { - t.Errorf("%s: Cannot sync connection pooler, %s", testName, err) - } + for _, role := range []PostgresRole{Master, Replica} { + poolerLabels := cluster.labelsSet(false) + poolerLabels["application"] = "db-connection-pooler" + poolerLabels["connection-pooler"] = cluster.connectionPoolerName(role) - for _, role := range [2]PostgresRole{Master, Replica} { - err = cluster.deleteConnectionPooler(role) - if err != nil { - t.Errorf("%s: Cannot delete connection pooler, %s", testName, err) + if cluster.ConnectionPooler[role].Deployment == nil || !util.MapContains(cluster.ConnectionPooler[role].Deployment.Labels, poolerLabels) { + return fmt.Errorf("Deployment was not saved or labels not attached %s %s", role, cluster.ConnectionPooler[role].Deployment.Labels) + } + + if cluster.ConnectionPooler[role].Service == nil || !util.MapContains(cluster.ConnectionPooler[role].Service.Labels, poolerLabels) { + return fmt.Errorf("Service was not saved or labels not attached %s %s", role, cluster.ConnectionPooler[role].Service.Labels) } } + + return nil +} + +func MasterObjectsAreSaved(cluster *Cluster, err error, reason SyncReason) error { + if cluster.ConnectionPooler == nil { + return fmt.Errorf("Connection pooler resources are empty") + } + + poolerLabels := cluster.labelsSet(false) + poolerLabels["application"] = "db-connection-pooler" + poolerLabels["connection-pooler"] = cluster.connectionPoolerName(Master) + + if cluster.ConnectionPooler[Master].Deployment == nil || !util.MapContains(cluster.ConnectionPooler[Master].Deployment.Labels, poolerLabels) { + return fmt.Errorf("Deployment was not saved or labels not attached %s", cluster.ConnectionPooler[Master].Deployment.Labels) + } + + if cluster.ConnectionPooler[Master].Service == nil || !util.MapContains(cluster.ConnectionPooler[Master].Service.Labels, poolerLabels) { + return fmt.Errorf("Service was not saved or labels not attached %s", cluster.ConnectionPooler[Master].Service.Labels) + } + + return nil +} + +func ReplicaObjectsAreSaved(cluster *Cluster, err error, reason SyncReason) error { + if cluster.ConnectionPooler == nil { + return fmt.Errorf("Connection pooler resources are empty") + } + + poolerLabels := cluster.labelsSet(false) + poolerLabels["application"] = "db-connection-pooler" + poolerLabels["connection-pooler"] = cluster.connectionPoolerName(Replica) + + if cluster.ConnectionPooler[Replica].Deployment == nil || !util.MapContains(cluster.ConnectionPooler[Replica].Deployment.Labels, poolerLabels) { + return fmt.Errorf("Deployment was not saved or labels not attached %s", cluster.ConnectionPooler[Replica].Deployment.Labels) + } + + if cluster.ConnectionPooler[Replica].Service == nil || !util.MapContains(cluster.ConnectionPooler[Replica].Service.Labels, poolerLabels) { + return fmt.Errorf("Service was not saved or labels not attached %s", cluster.ConnectionPooler[Replica].Service.Labels) + } + + return nil +} + +func objectsAreDeleted(cluster *Cluster, err error, reason SyncReason) error { + for _, role := range [2]PostgresRole{Master, Replica} { + if cluster.ConnectionPooler[role] != nil && + (cluster.ConnectionPooler[role].Deployment != nil || cluster.ConnectionPooler[role].Service != nil) { + return fmt.Errorf("Connection pooler was not deleted for role %v", role) + } + } + + return nil +} + +func OnlyMasterDeleted(cluster *Cluster, err error, reason SyncReason) error { + + if cluster.ConnectionPooler[Master] != nil && + (cluster.ConnectionPooler[Master].Deployment != nil || cluster.ConnectionPooler[Master].Service != nil) { + return fmt.Errorf("Connection pooler master was not deleted") + } + return nil +} + +func OnlyReplicaDeleted(cluster *Cluster, err error, reason SyncReason) error { + + if cluster.ConnectionPooler[Replica] != nil && + (cluster.ConnectionPooler[Replica].Deployment != nil || cluster.ConnectionPooler[Replica].Service != nil) { + return fmt.Errorf("Connection pooler replica was not deleted") + } + return nil +} + +func noEmptySync(cluster *Cluster, err error, reason SyncReason) error { + for _, msg := range reason { + if strings.HasPrefix(msg, "update [] from '' to '") { + return fmt.Errorf("There is an empty reason, %s", msg) + } + } + + return nil } func TestNeedConnectionPooler(t *testing.T) { @@ -210,133 +257,178 @@ func TestNeedConnectionPooler(t *testing.T) { } } -func deploymentUpdated(cluster *Cluster, err error, reason SyncReason) error { - for _, role := range [2]PostgresRole{Master, Replica} { - if cluster.ConnectionPooler[role] != nil && cluster.ConnectionPooler[role].Deployment != nil && - (cluster.ConnectionPooler[role].Deployment.Spec.Replicas == nil || - *cluster.ConnectionPooler[role].Deployment.Spec.Replicas != 2) { - return fmt.Errorf("Wrong number of instances") - } - } - return nil -} +func TestConnectionPoolerCreateDeletion(t *testing.T) { -func objectsAreSaved(cluster *Cluster, err error, reason SyncReason) error { - if cluster.ConnectionPooler == nil { - return fmt.Errorf("Connection pooler resources are empty") + testName := "test connection pooler creation and deletion" + clientSet := fake.NewSimpleClientset() + acidClientSet := fakeacidv1.NewSimpleClientset() + namespace := "default" + + client := k8sutil.KubernetesClient{ + StatefulSetsGetter: clientSet.AppsV1(), + ServicesGetter: clientSet.CoreV1(), + DeploymentsGetter: clientSet.AppsV1(), + PostgresqlsGetter: acidClientSet.AcidV1(), + SecretsGetter: clientSet.CoreV1(), } - for _, role := range []PostgresRole{Master, Replica} { - if cluster.ConnectionPooler[role].Deployment == nil { - return fmt.Errorf("Deployment was not saved %s", role) - } - - if cluster.ConnectionPooler[role].Service == nil { - return fmt.Errorf("Service was not saved %s", role) - } - } - - return nil -} - -func MasterobjectsAreSaved(cluster *Cluster, err error, reason SyncReason) error { - if cluster.ConnectionPooler == nil { - return fmt.Errorf("Connection pooler resources are empty") - } - - if cluster.ConnectionPooler[Master].Deployment == nil { - return fmt.Errorf("Deployment was not saved") - } - - if cluster.ConnectionPooler[Master].Service == nil { - return fmt.Errorf("Service was not saved") - } - - return nil -} - -func ReplicaobjectsAreSaved(cluster *Cluster, err error, reason SyncReason) error { - if cluster.ConnectionPooler == nil { - return fmt.Errorf("Connection pooler resources are empty") - } - - if cluster.ConnectionPooler[Replica].Deployment == nil { - return fmt.Errorf("Deployment was not saved") - } - - if cluster.ConnectionPooler[Replica].Service == nil { - return fmt.Errorf("Service was not saved") - } - - return nil -} - -func objectsAreDeleted(cluster *Cluster, err error, reason SyncReason) error { - for _, role := range [2]PostgresRole{Master, Replica} { - if cluster.ConnectionPooler[role] != nil && - (cluster.ConnectionPooler[role].Deployment != nil || cluster.ConnectionPooler[role].Service != nil) { - return fmt.Errorf("Connection pooler was not deleted for role %v", role) - } - } - - return nil -} - -func OnlyMasterDeleted(cluster *Cluster, err error, reason SyncReason) error { - - if cluster.ConnectionPooler[Master] != nil && - (cluster.ConnectionPooler[Master].Deployment != nil || cluster.ConnectionPooler[Master].Service != nil) { - return fmt.Errorf("Connection pooler master was not deleted") - } - return nil -} - -func OnlyReplicaDeleted(cluster *Cluster, err error, reason SyncReason) error { - - if cluster.ConnectionPooler[Replica] != nil && - (cluster.ConnectionPooler[Replica].Deployment != nil || cluster.ConnectionPooler[Replica].Service != nil) { - return fmt.Errorf("Connection pooler replica was not deleted") - } - return nil -} - -func noEmptySync(cluster *Cluster, err error, reason SyncReason) error { - for _, msg := range reason { - if strings.HasPrefix(msg, "update [] from '' to '") { - return fmt.Errorf("There is an empty reason, %s", msg) - } - } - - return nil -} - -func TestConnectionPoolerSynchronization(t *testing.T) { - testName := "Test connection pooler synchronization" - newCluster := func(client k8sutil.KubernetesClient) *Cluster { - return New( - Config{ - OpConfig: config.Config{ - ProtectedRoles: []string{"admin"}, - Auth: config.Auth{ - SuperUsername: superUserName, - ReplicationUsername: replicationUserName, - }, - ConnectionPooler: config.ConnectionPooler{ - ConnectionPoolerDefaultCPURequest: "100m", - ConnectionPoolerDefaultCPULimit: "100m", - ConnectionPoolerDefaultMemoryRequest: "100Mi", - ConnectionPoolerDefaultMemoryLimit: "100Mi", - NumberOfInstances: int32ToPointer(1), - }, - }, - }, client, acidv1.Postgresql{}, logger, eventRecorder) - } - cluster := newCluster(k8sutil.KubernetesClient{}) - - cluster.Statefulset = &appsv1.StatefulSet{ + pg := acidv1.Postgresql{ ObjectMeta: metav1.ObjectMeta{ - Name: "test-sts", + Name: "acid-fake-cluster", + Namespace: namespace, }, + Spec: acidv1.PostgresSpec{ + EnableConnectionPooler: boolToPointer(true), + EnableReplicaConnectionPooler: boolToPointer(true), + Volume: acidv1.Volume{ + Size: "1Gi", + }, + }, + } + + var cluster = New( + Config{ + OpConfig: config.Config{ + ConnectionPooler: config.ConnectionPooler{ + ConnectionPoolerDefaultCPURequest: "100m", + ConnectionPoolerDefaultCPULimit: "100m", + ConnectionPoolerDefaultMemoryRequest: "100Mi", + ConnectionPoolerDefaultMemoryLimit: "100Mi", + NumberOfInstances: int32ToPointer(1), + }, + PodManagementPolicy: "ordered_ready", + Resources: config.Resources{ + ClusterLabels: map[string]string{"application": "spilo"}, + ClusterNameLabel: "cluster-name", + DefaultCPURequest: "300m", + DefaultCPULimit: "300m", + DefaultMemoryRequest: "300Mi", + DefaultMemoryLimit: "300Mi", + PodRoleLabel: "spilo-role", + }, + }, + }, client, pg, logger, eventRecorder) + + cluster.Name = "acid-fake-cluster" + cluster.Namespace = "default" + + _, err := cluster.createService(Master) + assert.NoError(t, err) + _, err = cluster.createStatefulSet() + assert.NoError(t, err) + + reason, err := cluster.createConnectionPooler(mockInstallLookupFunction) + + if err != nil { + t.Errorf("%s: Cannot create connection pooler, %s, %+v", + testName, err, reason) + } + for _, role := range [2]PostgresRole{Master, Replica} { + poolerLabels := cluster.labelsSet(false) + poolerLabels["application"] = "db-connection-pooler" + poolerLabels["connection-pooler"] = cluster.connectionPoolerName(role) + + if cluster.ConnectionPooler[role] != nil { + if cluster.ConnectionPooler[role].Deployment == nil && util.MapContains(cluster.ConnectionPooler[role].Deployment.Labels, poolerLabels) { + t.Errorf("%s: Connection pooler deployment is empty for role %s", testName, role) + } + + if cluster.ConnectionPooler[role].Service == nil && util.MapContains(cluster.ConnectionPooler[role].Service.Labels, poolerLabels) { + t.Errorf("%s: Connection pooler service is empty for role %s", testName, role) + } + } + } + + oldSpec := &acidv1.Postgresql{ + Spec: acidv1.PostgresSpec{ + EnableConnectionPooler: boolToPointer(true), + EnableReplicaConnectionPooler: boolToPointer(true), + }, + } + newSpec := &acidv1.Postgresql{ + Spec: acidv1.PostgresSpec{ + EnableConnectionPooler: boolToPointer(false), + EnableReplicaConnectionPooler: boolToPointer(false), + }, + } + + // Delete connection pooler via sync + _, err = cluster.syncConnectionPooler(oldSpec, newSpec, mockInstallLookupFunction) + if err != nil { + t.Errorf("%s: Cannot sync connection pooler, %s", testName, err) + } + + for _, role := range [2]PostgresRole{Master, Replica} { + err = cluster.deleteConnectionPooler(role) + if err != nil { + t.Errorf("%s: Cannot delete connection pooler, %s", testName, err) + } + } +} + +func TestConnectionPoolerSync(t *testing.T) { + + testName := "test connection pooler synchronization" + clientSet := fake.NewSimpleClientset() + acidClientSet := fakeacidv1.NewSimpleClientset() + namespace := "default" + + client := k8sutil.KubernetesClient{ + StatefulSetsGetter: clientSet.AppsV1(), + ServicesGetter: clientSet.CoreV1(), + DeploymentsGetter: clientSet.AppsV1(), + PostgresqlsGetter: acidClientSet.AcidV1(), + SecretsGetter: clientSet.CoreV1(), + } + + pg := acidv1.Postgresql{ + ObjectMeta: metav1.ObjectMeta{ + Name: "acid-fake-cluster", + Namespace: namespace, + }, + Spec: acidv1.PostgresSpec{ + Volume: acidv1.Volume{ + Size: "1Gi", + }, + }, + } + + var cluster = New( + Config{ + OpConfig: config.Config{ + ConnectionPooler: config.ConnectionPooler{ + ConnectionPoolerDefaultCPURequest: "100m", + ConnectionPoolerDefaultCPULimit: "100m", + ConnectionPoolerDefaultMemoryRequest: "100Mi", + ConnectionPoolerDefaultMemoryLimit: "100Mi", + NumberOfInstances: int32ToPointer(1), + }, + PodManagementPolicy: "ordered_ready", + Resources: config.Resources{ + ClusterLabels: map[string]string{"application": "spilo"}, + ClusterNameLabel: "cluster-name", + DefaultCPURequest: "300m", + DefaultCPULimit: "300m", + DefaultMemoryRequest: "300Mi", + DefaultMemoryLimit: "300Mi", + PodRoleLabel: "spilo-role", + }, + }, + }, client, pg, logger, eventRecorder) + + cluster.Name = "acid-fake-cluster" + cluster.Namespace = "default" + + _, err := cluster.createService(Master) + assert.NoError(t, err) + _, err = cluster.createStatefulSet() + assert.NoError(t, err) + + reason, err := cluster.createConnectionPooler(mockInstallLookupFunction) + + if err != nil { + t.Errorf("%s: Cannot create connection pooler, %s, %+v", + testName, err, reason) } tests := []struct { @@ -358,10 +450,10 @@ func TestConnectionPoolerSynchronization(t *testing.T) { ConnectionPooler: &acidv1.ConnectionPooler{}, }, }, - cluster: newCluster(k8sutil.ClientMissingObjects()), + cluster: cluster, defaultImage: "pooler:1.0", defaultInstances: 1, - check: MasterobjectsAreSaved, + check: MasterObjectsAreSaved, }, { subTest: "create if doesn't exist", @@ -375,10 +467,10 @@ func TestConnectionPoolerSynchronization(t *testing.T) { ConnectionPooler: &acidv1.ConnectionPooler{}, }, }, - cluster: newCluster(k8sutil.ClientMissingObjects()), + cluster: cluster, defaultImage: "pooler:1.0", defaultInstances: 1, - check: MasterobjectsAreSaved, + check: MasterObjectsAreSaved, }, { subTest: "create if doesn't exist with a flag", @@ -390,10 +482,10 @@ func TestConnectionPoolerSynchronization(t *testing.T) { EnableConnectionPooler: boolToPointer(true), }, }, - cluster: newCluster(k8sutil.ClientMissingObjects()), + cluster: cluster, defaultImage: "pooler:1.0", defaultInstances: 1, - check: MasterobjectsAreSaved, + check: MasterObjectsAreSaved, }, { subTest: "create no replica with flag", @@ -405,7 +497,7 @@ func TestConnectionPoolerSynchronization(t *testing.T) { EnableReplicaConnectionPooler: boolToPointer(false), }, }, - cluster: newCluster(k8sutil.NewMockKubernetesClient()), + cluster: cluster, defaultImage: "pooler:1.0", defaultInstances: 1, check: objectsAreDeleted, @@ -421,10 +513,10 @@ func TestConnectionPoolerSynchronization(t *testing.T) { EnableReplicaConnectionPooler: boolToPointer(true), }, }, - cluster: newCluster(k8sutil.NewMockKubernetesClient()), + cluster: cluster, defaultImage: "pooler:1.0", defaultInstances: 1, - check: ReplicaobjectsAreSaved, + check: ReplicaObjectsAreSaved, }, { subTest: "create both master and replica", @@ -438,7 +530,7 @@ func TestConnectionPoolerSynchronization(t *testing.T) { EnableConnectionPooler: boolToPointer(true), }, }, - cluster: newCluster(k8sutil.ClientMissingObjects()), + cluster: cluster, defaultImage: "pooler:1.0", defaultInstances: 1, check: objectsAreSaved, @@ -456,7 +548,7 @@ func TestConnectionPoolerSynchronization(t *testing.T) { ConnectionPooler: &acidv1.ConnectionPooler{}, }, }, - cluster: newCluster(k8sutil.NewMockKubernetesClient()), + cluster: cluster, defaultImage: "pooler:1.0", defaultInstances: 1, check: OnlyReplicaDeleted, @@ -474,7 +566,7 @@ func TestConnectionPoolerSynchronization(t *testing.T) { EnableReplicaConnectionPooler: boolToPointer(true), }, }, - cluster: newCluster(k8sutil.NewMockKubernetesClient()), + cluster: cluster, defaultImage: "pooler:1.0", defaultInstances: 1, check: OnlyMasterDeleted, @@ -489,7 +581,7 @@ func TestConnectionPoolerSynchronization(t *testing.T) { newSpec: &acidv1.Postgresql{ Spec: acidv1.PostgresSpec{}, }, - cluster: newCluster(k8sutil.NewMockKubernetesClient()), + cluster: cluster, defaultImage: "pooler:1.0", defaultInstances: 1, check: objectsAreDeleted, @@ -502,53 +594,11 @@ func TestConnectionPoolerSynchronization(t *testing.T) { newSpec: &acidv1.Postgresql{ Spec: acidv1.PostgresSpec{}, }, - cluster: newCluster(k8sutil.NewMockKubernetesClient()), + cluster: cluster, defaultImage: "pooler:1.0", defaultInstances: 1, check: objectsAreDeleted, }, - { - subTest: "update deployment", - oldSpec: &acidv1.Postgresql{ - Spec: acidv1.PostgresSpec{ - ConnectionPooler: &acidv1.ConnectionPooler{ - NumberOfInstances: int32ToPointer(1), - }, - }, - }, - newSpec: &acidv1.Postgresql{ - Spec: acidv1.PostgresSpec{ - ConnectionPooler: &acidv1.ConnectionPooler{ - NumberOfInstances: int32ToPointer(2), - }, - }, - }, - cluster: newCluster(k8sutil.NewMockKubernetesClient()), - defaultImage: "pooler:1.0", - defaultInstances: 1, - check: deploymentUpdated, - }, - { - subTest: "update deployment", - oldSpec: &acidv1.Postgresql{ - Spec: acidv1.PostgresSpec{ - ConnectionPooler: &acidv1.ConnectionPooler{ - NumberOfInstances: int32ToPointer(1), - }, - }, - }, - newSpec: &acidv1.Postgresql{ - Spec: acidv1.PostgresSpec{ - ConnectionPooler: &acidv1.ConnectionPooler{ - NumberOfInstances: int32ToPointer(2), - }, - }, - }, - cluster: newCluster(k8sutil.NewMockKubernetesClient()), - defaultImage: "pooler:1.0", - defaultInstances: 1, - check: deploymentUpdated, - }, { subTest: "update image from changed defaults", oldSpec: &acidv1.Postgresql{ @@ -561,7 +611,7 @@ func TestConnectionPoolerSynchronization(t *testing.T) { ConnectionPooler: &acidv1.ConnectionPooler{}, }, }, - cluster: newCluster(k8sutil.NewMockKubernetesClient()), + cluster: cluster, defaultImage: "pooler:2.0", defaultInstances: 2, check: deploymentUpdated, @@ -580,7 +630,7 @@ func TestConnectionPoolerSynchronization(t *testing.T) { ConnectionPooler: &acidv1.ConnectionPooler{}, }, }, - cluster: newCluster(k8sutil.NewMockKubernetesClient()), + cluster: cluster, defaultImage: "pooler:1.0", defaultInstances: 1, check: noEmptySync, @@ -591,6 +641,8 @@ func TestConnectionPoolerSynchronization(t *testing.T) { tt.cluster.OpConfig.ConnectionPooler.NumberOfInstances = int32ToPointer(tt.defaultInstances) + t.Logf("running test for %s [%s]", testName, tt.subTest) + reason, err := tt.cluster.syncConnectionPooler(tt.oldSpec, tt.newSpec, mockInstallLookupFunction) From 4ea0b5f432e3d1ff084d2904a81f12ad84d709dc Mon Sep 17 00:00:00 2001 From: Felix Kunde Date: Fri, 22 Jan 2021 14:06:19 +0100 Subject: [PATCH 22/35] set AllowPrivilegeEscalation on container securityContext (#1326) --- .../templates/clusterrole-postgres-pod.yaml | 2 ++ .../templates/clusterrole.yaml | 4 ++- manifests/operator-service-account-rbac.yaml | 36 +++++++++---------- pkg/cluster/k8sres.go | 5 +-- 4 files changed, 26 insertions(+), 21 deletions(-) diff --git a/charts/postgres-operator/templates/clusterrole-postgres-pod.yaml b/charts/postgres-operator/templates/clusterrole-postgres-pod.yaml index b3f9f08f5..33c43822f 100644 --- a/charts/postgres-operator/templates/clusterrole-postgres-pod.yaml +++ b/charts/postgres-operator/templates/clusterrole-postgres-pod.yaml @@ -63,6 +63,7 @@ rules: - services verbs: - create +{{- if toString .Values.configKubernetes.spilo_privileged | eq "true" }} # to run privileged pods - apiGroups: - extensions @@ -72,4 +73,5 @@ rules: - privileged verbs: - use +{{- end }} {{ end }} diff --git a/charts/postgres-operator/templates/clusterrole.yaml b/charts/postgres-operator/templates/clusterrole.yaml index 165cce7c6..885bad3f7 100644 --- a/charts/postgres-operator/templates/clusterrole.yaml +++ b/charts/postgres-operator/templates/clusterrole.yaml @@ -228,7 +228,8 @@ rules: verbs: - get - create -# to grant privilege to run privileged pods +{{- if toString .Values.configKubernetes.spilo_privileged | eq "true" }} +# to run privileged pods - apiGroups: - extensions resources: @@ -237,4 +238,5 @@ rules: - privileged verbs: - use +{{- end }} {{ end }} diff --git a/manifests/operator-service-account-rbac.yaml b/manifests/operator-service-account-rbac.yaml index 1ba5b4d23..f0307f6a0 100644 --- a/manifests/operator-service-account-rbac.yaml +++ b/manifests/operator-service-account-rbac.yaml @@ -203,15 +203,15 @@ rules: verbs: - get - create -# to grant privilege to run privileged pods -- apiGroups: - - extensions - resources: - - podsecuritypolicies - resourceNames: - - privileged - verbs: - - use +# to grant privilege to run privileged pods (not needed by default) +#- apiGroups: +# - extensions +# resources: +# - podsecuritypolicies +# resourceNames: +# - privileged +# verbs: +# - use --- apiVersion: rbac.authorization.k8s.io/v1 @@ -265,12 +265,12 @@ rules: - services verbs: - create -# to run privileged pods -- apiGroups: - - extensions - resources: - - podsecuritypolicies - resourceNames: - - privileged - verbs: - - use +# to grant privilege to run privileged pods (not needed by default) +#- apiGroups: +# - extensions +# resources: +# - podsecuritypolicies +# resourceNames: +# - privileged +# verbs: +# - use diff --git a/pkg/cluster/k8sres.go b/pkg/cluster/k8sres.go index 06b074b4c..83098b8a9 100644 --- a/pkg/cluster/k8sres.go +++ b/pkg/cluster/k8sres.go @@ -453,8 +453,9 @@ func generateContainer( VolumeMounts: volumeMounts, Env: envVars, SecurityContext: &v1.SecurityContext{ - Privileged: &privilegedMode, - ReadOnlyRootFilesystem: util.False(), + AllowPrivilegeEscalation: &privilegedMode, + Privileged: &privilegedMode, + ReadOnlyRootFilesystem: util.False(), }, } } From 4a88f00a3f8e56fde9bd31b9a7cd704cd6b949cc Mon Sep 17 00:00:00 2001 From: Jan Mussler Date: Mon, 25 Jan 2021 10:07:18 +0100 Subject: [PATCH 23/35] Full AWS gp3 support for iops and througput config. (#1261) Support new AWS EBS volume type `gp3` with `iops` and `throughput` in the manifest. Co-authored-by: Felix Kunde --- .../postgres-operator/crds/postgresqls.yaml | 4 + docs/developer.md | 18 ++ docs/reference/cluster_manifest.md | 12 +- docs/reference/operator_parameters.md | 11 +- go.mod | 2 +- go.sum | 8 +- manifests/complete-postgres-manifest.yaml | 2 + manifests/postgresql.crd.yaml | 4 + pkg/apis/acid.zalan.do/v1/crds.go | 6 + pkg/apis/acid.zalan.do/v1/postgresql_type.go | 1 + pkg/cluster/sync.go | 72 +---- pkg/cluster/volumes.go | 240 ++++++++++++-- pkg/cluster/volumes_test.go | 305 +++++++++++++++--- pkg/util/volumes/ebs.go | 13 +- pkg/util/volumes/volumes.go | 2 +- 15 files changed, 535 insertions(+), 165 deletions(-) diff --git a/charts/postgres-operator/crds/postgresqls.yaml b/charts/postgres-operator/crds/postgresqls.yaml index 13811936d..ad11f6407 100644 --- a/charts/postgres-operator/crds/postgresqls.yaml +++ b/charts/postgres-operator/crds/postgresqls.yaml @@ -557,6 +557,8 @@ spec: required: - size properties: + iops: + type: integer size: type: string pattern: '^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$' @@ -565,6 +567,8 @@ spec: type: string subPath: type: string + throughput: + type: integer status: type: object additionalProperties: diff --git a/docs/developer.md b/docs/developer.md index 3316ac4cc..8ab1e60bc 100644 --- a/docs/developer.md +++ b/docs/developer.md @@ -235,6 +235,24 @@ Then you can for example check the Patroni logs: kubectl logs acid-minimal-cluster-0 ``` +## Unit tests with Mocks and K8s Fake API + +Whenever possible you should rely on leveraging proper mocks and K8s fake client that allows full fledged testing of K8s objects in your unit tests. + +To enable mocks, a code annotation is needed: +[Mock code gen annotation](https://github.com/zalando/postgres-operator/blob/master/pkg/util/volumes/volumes.go#L3) + +To generate mocks run: +```bash +make mocks +``` + +Examples for mocks can be found in: +[Example mock usage](https://github.com/zalando/postgres-operator/blob/master/pkg/cluster/volumes_test.go#L248) + +Examples for fake K8s objects can be found in: +[Example fake K8s client usage](https://github.com/zalando/postgres-operator/blob/master/pkg/cluster/volumes_test.go#L166) + ## End-to-end tests The operator provides reference end-to-end (e2e) tests to diff --git a/docs/reference/cluster_manifest.md b/docs/reference/cluster_manifest.md index 589921bc5..1b2d71a66 100644 --- a/docs/reference/cluster_manifest.md +++ b/docs/reference/cluster_manifest.md @@ -338,13 +338,13 @@ archive is supported. the url to S3 bucket containing the WAL archive of the remote primary. Required when the `standby` section is present. -## EBS volume resizing +## Volume properties Those parameters are grouped under the `volume` top-level key and define the properties of the persistent storage that stores Postgres data. * **size** - the size of the target EBS volume. Usual Kubernetes size modifiers, i.e. `Gi` + the size of the target volume. Usual Kubernetes size modifiers, i.e. `Gi` or `Mi`, apply. Required. * **storageClass** @@ -356,6 +356,14 @@ properties of the persistent storage that stores Postgres data. * **subPath** Subpath to use when mounting volume into Spilo container. Optional. +* **iops** + When running the operator on AWS the latest generation of EBS volumes (`gp3`) + allows for configuring the number of IOPS. Maximum is 16000. Optional. + +* **throughput** + When running the operator on AWS the latest generation of EBS volumes (`gp3`) + allows for configuring the throughput in MB/s. Maximum is 1000. Optional. + ## Sidecar definitions Those parameters are defined under the `sidecars` key. They consist of a list diff --git a/docs/reference/operator_parameters.md b/docs/reference/operator_parameters.md index 212515dc1..5c5850505 100644 --- a/docs/reference/operator_parameters.md +++ b/docs/reference/operator_parameters.md @@ -373,10 +373,13 @@ configuration they are grouped under the `kubernetes` key. possible value is `parallel`. * **storage_resize_mode** - defines how operator handels the difference between requested volume size and - actual size. Available options are: ebs - tries to resize EBS volume, pvc - - changes PVC definition, off - disables resize of the volumes. Default is "pvc". - When using OpenShift please use one of the other available options. + defines how operator handles the difference between the requested volume size and + the actual size. Available options are: + 1. `ebs` : operator resizes EBS volumes directly and executes `resizefs` within a pod + 2. `pvc` : operator only changes PVC definition + 3. `off` : disables resize of the volumes. + 4. `mixed` :operator uses AWS API to adjust size, throughput, and IOPS, and calls pvc change for file system resize + Default is "pvc". ## Kubernetes resource requests diff --git a/go.mod b/go.mod index 4e9c8a742..bbe5140b7 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/zalando/postgres-operator go 1.15 require ( - github.com/aws/aws-sdk-go v1.36.3 + github.com/aws/aws-sdk-go v1.36.29 github.com/golang/mock v1.4.4 github.com/lib/pq v1.9.0 github.com/motomux/pretty v0.0.0-20161209205251-b2aad2c9a95d diff --git a/go.sum b/go.sum index d8df3fda4..fa8d2b135 100644 --- a/go.sum +++ b/go.sum @@ -35,6 +35,8 @@ github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbt github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/Storytel/gomock-matchers v1.2.0 h1:VPsbL6c/9/eCa4rH13LOEXPsIsnA1z+INamGIx1lWQo= +github.com/Storytel/gomock-matchers v1.2.0/go.mod h1:7HEuwyU/eq/W3mrSqPSYETGXiTyU2um0Rrb+dh5KmKM= github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -45,8 +47,8 @@ github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5 github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= -github.com/aws/aws-sdk-go v1.36.3 h1:KYpG5OegwW3xgOsMxy01nj/Td281yxi1Ha2lJQJs4tI= -github.com/aws/aws-sdk-go v1.36.3/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= +github.com/aws/aws-sdk-go v1.36.29 h1:lM1G3AF1+7vzFm0n7hfH8r2+750BTo+6Lo6FtPB7kzk= +github.com/aws/aws-sdk-go v1.36.29/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -182,6 +184,7 @@ github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7 h1:5ZkaAPbicIKTF github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.1-0.20190311213431-837231f7bb37/go.mod h1:L3bP22mxdfCUHSUVMs+SPJMx55FrxQew7MSXT11Q86g= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= @@ -525,6 +528,7 @@ golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190221204921-83362c3779f5/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= diff --git a/manifests/complete-postgres-manifest.yaml b/manifests/complete-postgres-manifest.yaml index 412bac29b..f721f0ccb 100644 --- a/manifests/complete-postgres-manifest.yaml +++ b/manifests/complete-postgres-manifest.yaml @@ -44,6 +44,8 @@ spec: volume: size: 1Gi # storageClass: my-sc +# iops: 1000 # for EBS gp3 + # throughput: 250 # in MB/s for EBS gp3 additionalVolumes: - name: empty mountPath: /opt/empty diff --git a/manifests/postgresql.crd.yaml b/manifests/postgresql.crd.yaml index d5170e9d4..61a04144c 100644 --- a/manifests/postgresql.crd.yaml +++ b/manifests/postgresql.crd.yaml @@ -553,6 +553,8 @@ spec: required: - size properties: + iops: + type: integer size: type: string pattern: '^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$' @@ -561,6 +563,8 @@ spec: type: string subPath: type: string + throughput: + type: integer status: type: object additionalProperties: diff --git a/pkg/apis/acid.zalan.do/v1/crds.go b/pkg/apis/acid.zalan.do/v1/crds.go index f03b4c2ab..02d40342f 100644 --- a/pkg/apis/acid.zalan.do/v1/crds.go +++ b/pkg/apis/acid.zalan.do/v1/crds.go @@ -835,6 +835,9 @@ var PostgresCRDResourceValidation = apiextv1.CustomResourceValidation{ Type: "object", Required: []string{"size"}, Properties: map[string]apiextv1.JSONSchemaProps{ + "iops": { + Type: "integer", + }, "size": { Type: "string", Description: "Value must not be zero", @@ -846,6 +849,9 @@ var PostgresCRDResourceValidation = apiextv1.CustomResourceValidation{ "subPath": { Type: "string", }, + "throughput": { + Type: "integer", + }, }, }, }, diff --git a/pkg/apis/acid.zalan.do/v1/postgresql_type.go b/pkg/apis/acid.zalan.do/v1/postgresql_type.go index bdae22a7c..7346fb0e5 100644 --- a/pkg/apis/acid.zalan.do/v1/postgresql_type.go +++ b/pkg/apis/acid.zalan.do/v1/postgresql_type.go @@ -118,6 +118,7 @@ type Volume struct { SubPath string `json:"subPath,omitempty"` Iops *int64 `json:"iops,omitempty"` Throughput *int64 `json:"throughput,omitempty"` + VolumeType string `json:"type,omitempty"` } // AdditionalVolume specs additional optional volumes for statefulset diff --git a/pkg/cluster/sync.go b/pkg/cluster/sync.go index dc54ae8ee..5c0f6ce84 100644 --- a/pkg/cluster/sync.go +++ b/pkg/cluster/sync.go @@ -53,8 +53,6 @@ func (c *Cluster) Sync(newSpec *acidv1.Postgresql) error { return err } - c.logger.Debugf("syncing volumes using %q storage resize mode", c.OpConfig.StorageResizeMode) - if c.OpConfig.EnableEBSGp3Migration { err = c.executeEBSMigration() if nil != err { @@ -62,32 +60,8 @@ func (c *Cluster) Sync(newSpec *acidv1.Postgresql) error { } } - if c.OpConfig.StorageResizeMode == "mixed" { - // mixed op uses AWS API to adjust size,throughput,iops and calls pvc chance for file system resize - - // resize pvc to adjust filesystem size until better K8s support - if err = c.syncVolumeClaims(); err != nil { - err = fmt.Errorf("could not sync persistent volume claims: %v", err) - return err - } - } else if c.OpConfig.StorageResizeMode == "pvc" { - if err = c.syncVolumeClaims(); err != nil { - err = fmt.Errorf("could not sync persistent volume claims: %v", err) - return err - } - } else if c.OpConfig.StorageResizeMode == "ebs" { - // potentially enlarge volumes before changing the statefulset. By doing that - // in this order we make sure the operator is not stuck waiting for a pod that - // cannot start because it ran out of disk space. - // TODO: handle the case of the cluster that is downsized and enlarged again - // (there will be a volume from the old pod for which we can't act before the - // the statefulset modification is concluded) - if err = c.syncVolumes(); err != nil { - err = fmt.Errorf("could not sync persistent volumes: %v", err) - return err - } - } else { - c.logger.Infof("Storage resize is disabled (storage_resize_mode is off). Skipping volume sync.") + if err = c.syncVolumes(); err != nil { + return err } if err = c.enforceMinResourceLimits(&c.Spec); err != nil { @@ -590,48 +564,6 @@ func (c *Cluster) syncRoles() (err error) { return nil } -// syncVolumeClaims reads all persistent volume claims and checks that their size matches the one declared in the statefulset. -func (c *Cluster) syncVolumeClaims() error { - c.setProcessName("syncing volume claims") - - act, err := c.volumeClaimsNeedResizing(c.Spec.Volume) - if err != nil { - return fmt.Errorf("could not compare size of the volume claims: %v", err) - } - if !act { - c.logger.Infof("volume claims do not require changes") - return nil - } - if err := c.resizeVolumeClaims(c.Spec.Volume); err != nil { - return fmt.Errorf("could not sync volume claims: %v", err) - } - - c.logger.Infof("volume claims have been synced successfully") - - return nil -} - -// syncVolumes reads all persistent volumes and checks that their size matches the one declared in the statefulset. -func (c *Cluster) syncVolumes() error { - c.setProcessName("syncing volumes") - - act, err := c.volumesNeedResizing(c.Spec.Volume) - if err != nil { - return fmt.Errorf("could not compare size of the volumes: %v", err) - } - if !act { - return nil - } - - if err := c.resizeVolumes(); err != nil { - return fmt.Errorf("could not sync volumes: %v", err) - } - - c.logger.Infof("volumes have been synced successfully") - - return nil -} - func (c *Cluster) syncDatabases() error { c.setProcessName("syncing databases") diff --git a/pkg/cluster/volumes.go b/pkg/cluster/volumes.go index 162d24075..e07d453ec 100644 --- a/pkg/cluster/volumes.go +++ b/pkg/cluster/volumes.go @@ -10,13 +10,215 @@ import ( "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/aws/aws-sdk-go/aws" acidv1 "github.com/zalando/postgres-operator/pkg/apis/acid.zalan.do/v1" "github.com/zalando/postgres-operator/pkg/spec" "github.com/zalando/postgres-operator/pkg/util" "github.com/zalando/postgres-operator/pkg/util/constants" "github.com/zalando/postgres-operator/pkg/util/filesystems" + "github.com/zalando/postgres-operator/pkg/util/volumes" ) +func (c *Cluster) syncVolumes() error { + c.logger.Debugf("syncing volumes using %q storage resize mode", c.OpConfig.StorageResizeMode) + var err error + + // check quantity string once, and do not bother with it anymore anywhere else + _, err = resource.ParseQuantity(c.Spec.Volume.Size) + if err != nil { + return fmt.Errorf("could not parse volume size from the manifest: %v", err) + } + + if c.OpConfig.StorageResizeMode == "mixed" { + // mixed op uses AWS API to adjust size, throughput, iops, and calls pvc change for file system resize + // in case of errors we proceed to let K8s do its work, favoring disk space increase of other adjustments + + err = c.populateVolumeMetaData() + if err != nil { + c.logger.Errorf("populating EBS meta data failed, skipping potential adjustements: %v", err) + } else { + err = c.syncUnderlyingEBSVolume() + if err != nil { + c.logger.Errorf("errors occured during EBS volume adjustments: %v", err) + } + } + + // resize pvc to adjust filesystem size until better K8s support + if err = c.syncVolumeClaims(); err != nil { + err = fmt.Errorf("could not sync persistent volume claims: %v", err) + return err + } + } else if c.OpConfig.StorageResizeMode == "pvc" { + if err = c.syncVolumeClaims(); err != nil { + err = fmt.Errorf("could not sync persistent volume claims: %v", err) + return err + } + } else if c.OpConfig.StorageResizeMode == "ebs" { + // potentially enlarge volumes before changing the statefulset. By doing that + // in this order we make sure the operator is not stuck waiting for a pod that + // cannot start because it ran out of disk space. + // TODO: handle the case of the cluster that is downsized and enlarged again + // (there will be a volume from the old pod for which we can't act before the + // the statefulset modification is concluded) + if err = c.syncEbsVolumes(); err != nil { + err = fmt.Errorf("could not sync persistent volumes: %v", err) + return err + } + } else { + c.logger.Infof("Storage resize is disabled (storage_resize_mode is off). Skipping volume sync.") + } + + return nil +} + +func (c *Cluster) syncUnderlyingEBSVolume() error { + c.logger.Infof("starting to sync EBS volumes: type, iops, throughput, and size") + + var err error + + targetValue := c.Spec.Volume + newSize, err := resource.ParseQuantity(targetValue.Size) + targetSize := quantityToGigabyte(newSize) + + awsGp3 := aws.String("gp3") + awsIo2 := aws.String("io2") + + errors := []string{} + + for _, volume := range c.EBSVolumes { + var modifyIops *int64 + var modifyThroughput *int64 + var modifySize *int64 + var modifyType *string + + if targetValue.Iops != nil { + if volume.Iops != *targetValue.Iops { + modifyIops = targetValue.Iops + } + } + + if targetValue.Throughput != nil { + if volume.Throughput != *targetValue.Throughput { + modifyThroughput = targetValue.Throughput + } + } + + if targetSize > volume.Size { + modifySize = &targetSize + } + + if modifyIops != nil || modifyThroughput != nil || modifySize != nil { + if modifyIops != nil || modifyThroughput != nil { + // we default to gp3 if iops and throughput are configured + modifyType = awsGp3 + if targetValue.VolumeType == "io2" { + modifyType = awsIo2 + } + } else if targetValue.VolumeType == "gp3" && volume.VolumeType != "gp3" { + modifyType = awsGp3 + } else { + // do not touch type + modifyType = nil + } + + err = c.VolumeResizer.ModifyVolume(volume.VolumeID, modifyType, modifySize, modifyIops, modifyThroughput) + if err != nil { + errors = append(errors, fmt.Sprintf("modify volume failed: volume=%s size=%d iops=%d throughput=%d", volume.VolumeID, volume.Size, volume.Iops, volume.Throughput)) + } + } + } + + if len(errors) > 0 { + for _, s := range errors { + c.logger.Warningf(s) + } + // c.logger.Errorf("failed to modify %d of %d volumes", len(c.EBSVolumes), len(errors)) + } + return nil +} + +func (c *Cluster) populateVolumeMetaData() error { + c.logger.Infof("starting reading ebs meta data") + + pvs, err := c.listPersistentVolumes() + if err != nil { + return fmt.Errorf("could not list persistent volumes: %v", err) + } + c.logger.Debugf("found %d volumes, size of known volumes %d", len(pvs), len(c.EBSVolumes)) + + volumeIds := []string{} + var volumeID string + for _, pv := range pvs { + volumeID, err = c.VolumeResizer.ExtractVolumeID(pv.Spec.AWSElasticBlockStore.VolumeID) + if err != nil { + continue + } + + volumeIds = append(volumeIds, volumeID) + } + + currentVolumes, err := c.VolumeResizer.DescribeVolumes(volumeIds) + if nil != err { + return err + } + + if len(currentVolumes) != len(c.EBSVolumes) { + c.logger.Debugf("number of ebs volumes (%d) discovered differs from already known volumes (%d)", len(currentVolumes), len(c.EBSVolumes)) + } + + // reset map, operator is not responsible for dangling ebs volumes + c.EBSVolumes = make(map[string]volumes.VolumeProperties) + for _, volume := range currentVolumes { + c.EBSVolumes[volume.VolumeID] = volume + } + + return nil +} + +// syncVolumeClaims reads all persistent volume claims and checks that their size matches the one declared in the statefulset. +func (c *Cluster) syncVolumeClaims() error { + c.setProcessName("syncing volume claims") + + needsResizing, err := c.volumeClaimsNeedResizing(c.Spec.Volume) + if err != nil { + return fmt.Errorf("could not compare size of the volume claims: %v", err) + } + + if !needsResizing { + c.logger.Infof("volume claims do not require changes") + return nil + } + + if err := c.resizeVolumeClaims(c.Spec.Volume); err != nil { + return fmt.Errorf("could not sync volume claims: %v", err) + } + + c.logger.Infof("volume claims have been synced successfully") + + return nil +} + +// syncVolumes reads all persistent volumes and checks that their size matches the one declared in the statefulset. +func (c *Cluster) syncEbsVolumes() error { + c.setProcessName("syncing EBS and Claims volumes") + + act, err := c.volumesNeedResizing() + if err != nil { + return fmt.Errorf("could not compare size of the volumes: %v", err) + } + if !act { + return nil + } + + if err := c.resizeVolumes(); err != nil { + return fmt.Errorf("could not sync volumes: %v", err) + } + + c.logger.Infof("volumes have been synced successfully") + + return nil +} + func (c *Cluster) listPersistentVolumeClaims() ([]v1.PersistentVolumeClaim, error) { ns := c.Namespace listOptions := metav1.ListOptions{ @@ -125,15 +327,16 @@ func (c *Cluster) resizeVolumes() error { c.setProcessName("resizing EBS volumes") - resizer := c.VolumeResizer - var totalIncompatible int - newQuantity, err := resource.ParseQuantity(c.Spec.Volume.Size) if err != nil { return fmt.Errorf("could not parse volume size: %v", err) } - pvs, newSize, err := c.listVolumesWithManifestSize(c.Spec.Volume) + newSize := quantityToGigabyte(newQuantity) + resizer := c.VolumeResizer + var totalIncompatible int + + pvs, err := c.listPersistentVolumes() if err != nil { return fmt.Errorf("could not list persistent volumes: %v", err) } @@ -214,33 +417,23 @@ func (c *Cluster) volumeClaimsNeedResizing(newVolume acidv1.Volume) (bool, error return false, nil } -func (c *Cluster) volumesNeedResizing(newVolume acidv1.Volume) (bool, error) { - vols, manifestSize, err := c.listVolumesWithManifestSize(newVolume) +func (c *Cluster) volumesNeedResizing() (bool, error) { + newQuantity, _ := resource.ParseQuantity(c.Spec.Volume.Size) + newSize := quantityToGigabyte(newQuantity) + + vols, err := c.listPersistentVolumes() if err != nil { return false, err } for _, pv := range vols { currentSize := quantityToGigabyte(pv.Spec.Capacity[v1.ResourceStorage]) - if currentSize != manifestSize { + if currentSize != newSize { return true, nil } } return false, nil } -func (c *Cluster) listVolumesWithManifestSize(newVolume acidv1.Volume) ([]*v1.PersistentVolume, int64, error) { - newSize, err := resource.ParseQuantity(newVolume.Size) - if err != nil { - return nil, 0, fmt.Errorf("could not parse volume size from the manifest: %v", err) - } - manifestSize := quantityToGigabyte(newSize) - vols, err := c.listPersistentVolumes() - if err != nil { - return nil, 0, fmt.Errorf("could not list persistent volumes: %v", err) - } - return vols, manifestSize, nil -} - // getPodNameFromPersistentVolume returns a pod name that it extracts from the volume claim ref. func getPodNameFromPersistentVolume(pv *v1.PersistentVolume) *spec.NamespacedName { namespace := pv.Spec.ClaimRef.Namespace @@ -258,7 +451,7 @@ func (c *Cluster) executeEBSMigration() error { } c.logger.Infof("starting EBS gp2 to gp3 migration") - pvs, _, err := c.listVolumesWithManifestSize(c.Spec.Volume) + pvs, err := c.listPersistentVolumes() if err != nil { return fmt.Errorf("could not list persistent volumes: %v", err) } @@ -294,10 +487,13 @@ func (c *Cluster) executeEBSMigration() error { return err } + var i3000 int64 = 3000 + var i125 int64 = 125 + for _, volume := range awsVolumes { if volume.VolumeType == "gp2" && volume.Size < c.OpConfig.EnableEBSGp3MigrationMaxSize { c.logger.Infof("modifying EBS volume %s to type gp3 migration (%d)", volume.VolumeID, volume.Size) - err = c.VolumeResizer.ModifyVolume(volume.VolumeID, "gp3", volume.Size, 3000, 125) + err = c.VolumeResizer.ModifyVolume(volume.VolumeID, aws.String("gp3"), &volume.Size, &i3000, &i125) if nil != err { c.logger.Warningf("modifying volume %s failed: %v", volume.VolumeID, err) } diff --git a/pkg/cluster/volumes_test.go b/pkg/cluster/volumes_test.go index 17fb8a4af..aea7711af 100644 --- a/pkg/cluster/volumes_test.go +++ b/pkg/cluster/volumes_test.go @@ -11,7 +11,9 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" + "github.com/aws/aws-sdk-go/aws" "github.com/golang/mock/gomock" + "github.com/stretchr/testify/assert" "github.com/zalando/postgres-operator/mocks" acidv1 "github.com/zalando/postgres-operator/pkg/apis/acid.zalan.do/v1" @@ -187,60 +189,16 @@ func TestMigrateEBS(t *testing.T) { cluster.Namespace = namespace filterLabels := cluster.labelsSet(false) - pvcList := CreatePVCs(namespace, clusterName, filterLabels, 2, "1Gi") - - ps := v1.PersistentVolumeSpec{} - ps.AWSElasticBlockStore = &v1.AWSElasticBlockStoreVolumeSource{} - ps.AWSElasticBlockStore.VolumeID = "aws://eu-central-1b/ebs-volume-1" - - ps2 := v1.PersistentVolumeSpec{} - ps2.AWSElasticBlockStore = &v1.AWSElasticBlockStoreVolumeSource{} - ps2.AWSElasticBlockStore.VolumeID = "aws://eu-central-1b/ebs-volume-2" - - pvList := &v1.PersistentVolumeList{ - Items: []v1.PersistentVolume{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "persistent-volume-0", - }, - Spec: ps, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "persistent-volume-1", - }, - Spec: ps2, - }, + testVolumes := []testVolume{ + { + size: 100, + }, + { + size: 100, }, } - for _, pvc := range pvcList.Items { - cluster.KubeClient.PersistentVolumeClaims(namespace).Create(context.TODO(), &pvc, metav1.CreateOptions{}) - } - - for _, pv := range pvList.Items { - cluster.KubeClient.PersistentVolumes().Create(context.TODO(), &pv, metav1.CreateOptions{}) - } - - pod := v1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: clusterName + "-0", - Labels: filterLabels, - }, - Spec: v1.PodSpec{}, - } - - cluster.KubeClient.Pods(namespace).Create(context.TODO(), &pod, metav1.CreateOptions{}) - - pod = v1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: clusterName + "-1", - Labels: filterLabels, - }, - Spec: v1.PodSpec{}, - } - - cluster.KubeClient.Pods(namespace).Create(context.TODO(), &pod, metav1.CreateOptions{}) + initTestVolumesAndPods(cluster.KubeClient, namespace, clusterName, filterLabels, testVolumes) ctrl := gomock.NewController(t) defer ctrl.Finish() @@ -256,8 +214,251 @@ func TestMigrateEBS(t *testing.T) { {VolumeID: "ebs-volume-2", VolumeType: "gp3", Size: 100}}, nil) // expect only gp2 volume to be modified - resizer.EXPECT().ModifyVolume(gomock.Eq("ebs-volume-1"), gomock.Eq("gp3"), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil) + resizer.EXPECT().ModifyVolume(gomock.Eq("ebs-volume-1"), gomock.Eq(aws.String("gp3")), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil) cluster.VolumeResizer = resizer cluster.executeEBSMigration() } + +type testVolume struct { + iops int64 + throughtput int64 + size int64 + volType string +} + +func initTestVolumesAndPods(client k8sutil.KubernetesClient, namespace, clustername string, labels labels.Set, volumes []testVolume) { + i := 0 + for _, v := range volumes { + storage1Gi, _ := resource.ParseQuantity(fmt.Sprintf("%d", v.size)) + + ps := v1.PersistentVolumeSpec{} + ps.AWSElasticBlockStore = &v1.AWSElasticBlockStoreVolumeSource{} + ps.AWSElasticBlockStore.VolumeID = fmt.Sprintf("aws://eu-central-1b/ebs-volume-%d", i+1) + + pv := v1.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("persistent-volume-%d", i), + }, + Spec: ps, + } + + client.PersistentVolumes().Create(context.TODO(), &pv, metav1.CreateOptions{}) + + pvc := v1.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("%s-%s-%d", constants.DataVolumeName, clustername, i), + Namespace: namespace, + Labels: labels, + }, + Spec: v1.PersistentVolumeClaimSpec{ + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceStorage: storage1Gi, + }, + }, + VolumeName: fmt.Sprintf("persistent-volume-%d", i), + }, + } + + client.PersistentVolumeClaims(namespace).Create(context.TODO(), &pvc, metav1.CreateOptions{}) + + pod := v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("%s-%d", clustername, i), + Labels: labels, + }, + Spec: v1.PodSpec{}, + } + + client.Pods(namespace).Create(context.TODO(), &pod, metav1.CreateOptions{}) + + i = i + 1 + } +} + +func TestMigrateGp3Support(t *testing.T) { + client, _ := newFakeK8sPVCclient() + clusterName := "acid-test-cluster" + namespace := "default" + + // new cluster with pvc storage resize mode and configured labels + var cluster = New( + Config{ + OpConfig: config.Config{ + Resources: config.Resources{ + ClusterLabels: map[string]string{"application": "spilo"}, + ClusterNameLabel: "cluster-name", + }, + StorageResizeMode: "mixed", + EnableEBSGp3Migration: false, + EnableEBSGp3MigrationMaxSize: 1000, + }, + }, client, acidv1.Postgresql{}, logger, eventRecorder) + + cluster.Spec.Volume.Size = "150Gi" + cluster.Spec.Volume.Iops = aws.Int64(6000) + cluster.Spec.Volume.Throughput = aws.Int64(275) + + // set metadata, so that labels will get correct values + cluster.Name = clusterName + cluster.Namespace = namespace + filterLabels := cluster.labelsSet(false) + + testVolumes := []testVolume{ + { + size: 100, + }, + { + size: 100, + }, + { + size: 100, + }, + } + + initTestVolumesAndPods(cluster.KubeClient, namespace, clusterName, filterLabels, testVolumes) + + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + resizer := mocks.NewMockVolumeResizer(ctrl) + + resizer.EXPECT().ExtractVolumeID(gomock.Eq("aws://eu-central-1b/ebs-volume-1")).Return("ebs-volume-1", nil) + resizer.EXPECT().ExtractVolumeID(gomock.Eq("aws://eu-central-1b/ebs-volume-2")).Return("ebs-volume-2", nil) + resizer.EXPECT().ExtractVolumeID(gomock.Eq("aws://eu-central-1b/ebs-volume-3")).Return("ebs-volume-3", nil) + + resizer.EXPECT().DescribeVolumes(gomock.Eq([]string{"ebs-volume-1", "ebs-volume-2", "ebs-volume-3"})).Return( + []volumes.VolumeProperties{ + {VolumeID: "ebs-volume-1", VolumeType: "gp3", Size: 100, Iops: 3000}, + {VolumeID: "ebs-volume-2", VolumeType: "gp3", Size: 105, Iops: 4000}, + {VolumeID: "ebs-volume-3", VolumeType: "gp3", Size: 151, Iops: 6000, Throughput: 275}}, nil) + + // expect only gp2 volume to be modified + resizer.EXPECT().ModifyVolume(gomock.Eq("ebs-volume-1"), gomock.Eq(aws.String("gp3")), gomock.Eq(aws.Int64(150)), gomock.Eq(aws.Int64(6000)), gomock.Eq(aws.Int64(275))).Return(nil) + resizer.EXPECT().ModifyVolume(gomock.Eq("ebs-volume-2"), gomock.Eq(aws.String("gp3")), gomock.Eq(aws.Int64(150)), gomock.Eq(aws.Int64(6000)), gomock.Eq(aws.Int64(275))).Return(nil) + // resizer.EXPECT().ModifyVolume(gomock.Eq("ebs-volume-3"), gomock.Eq(aws.String("gp3")), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil) + + cluster.VolumeResizer = resizer + cluster.syncVolumes() +} + +func TestManualGp2Gp3Support(t *testing.T) { + client, _ := newFakeK8sPVCclient() + clusterName := "acid-test-cluster" + namespace := "default" + + // new cluster with pvc storage resize mode and configured labels + var cluster = New( + Config{ + OpConfig: config.Config{ + Resources: config.Resources{ + ClusterLabels: map[string]string{"application": "spilo"}, + ClusterNameLabel: "cluster-name", + }, + StorageResizeMode: "mixed", + EnableEBSGp3Migration: false, + EnableEBSGp3MigrationMaxSize: 1000, + }, + }, client, acidv1.Postgresql{}, logger, eventRecorder) + + cluster.Spec.Volume.Size = "150Gi" + cluster.Spec.Volume.Iops = aws.Int64(6000) + cluster.Spec.Volume.Throughput = aws.Int64(275) + + // set metadata, so that labels will get correct values + cluster.Name = clusterName + cluster.Namespace = namespace + filterLabels := cluster.labelsSet(false) + + testVolumes := []testVolume{ + { + size: 100, + }, + { + size: 100, + }, + } + + initTestVolumesAndPods(cluster.KubeClient, namespace, clusterName, filterLabels, testVolumes) + + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + resizer := mocks.NewMockVolumeResizer(ctrl) + + resizer.EXPECT().ExtractVolumeID(gomock.Eq("aws://eu-central-1b/ebs-volume-1")).Return("ebs-volume-1", nil) + resizer.EXPECT().ExtractVolumeID(gomock.Eq("aws://eu-central-1b/ebs-volume-2")).Return("ebs-volume-2", nil) + + resizer.EXPECT().DescribeVolumes(gomock.Eq([]string{"ebs-volume-1", "ebs-volume-2"})).Return( + []volumes.VolumeProperties{ + {VolumeID: "ebs-volume-1", VolumeType: "gp2", Size: 150, Iops: 3000}, + {VolumeID: "ebs-volume-2", VolumeType: "gp2", Size: 150, Iops: 4000}, + }, nil) + + // expect only gp2 volume to be modified + resizer.EXPECT().ModifyVolume(gomock.Eq("ebs-volume-1"), gomock.Eq(aws.String("gp3")), gomock.Nil(), gomock.Eq(aws.Int64(6000)), gomock.Eq(aws.Int64(275))).Return(nil) + resizer.EXPECT().ModifyVolume(gomock.Eq("ebs-volume-2"), gomock.Eq(aws.String("gp3")), gomock.Nil(), gomock.Eq(aws.Int64(6000)), gomock.Eq(aws.Int64(275))).Return(nil) + + cluster.VolumeResizer = resizer + cluster.syncVolumes() +} + +func TestDontTouchType(t *testing.T) { + client, _ := newFakeK8sPVCclient() + clusterName := "acid-test-cluster" + namespace := "default" + + // new cluster with pvc storage resize mode and configured labels + var cluster = New( + Config{ + OpConfig: config.Config{ + Resources: config.Resources{ + ClusterLabels: map[string]string{"application": "spilo"}, + ClusterNameLabel: "cluster-name", + }, + StorageResizeMode: "mixed", + EnableEBSGp3Migration: false, + EnableEBSGp3MigrationMaxSize: 1000, + }, + }, client, acidv1.Postgresql{}, logger, eventRecorder) + + cluster.Spec.Volume.Size = "177Gi" + + // set metadata, so that labels will get correct values + cluster.Name = clusterName + cluster.Namespace = namespace + filterLabels := cluster.labelsSet(false) + + testVolumes := []testVolume{ + { + size: 150, + }, + { + size: 150, + }, + } + + initTestVolumesAndPods(cluster.KubeClient, namespace, clusterName, filterLabels, testVolumes) + + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + resizer := mocks.NewMockVolumeResizer(ctrl) + + resizer.EXPECT().ExtractVolumeID(gomock.Eq("aws://eu-central-1b/ebs-volume-1")).Return("ebs-volume-1", nil) + resizer.EXPECT().ExtractVolumeID(gomock.Eq("aws://eu-central-1b/ebs-volume-2")).Return("ebs-volume-2", nil) + + resizer.EXPECT().DescribeVolumes(gomock.Eq([]string{"ebs-volume-1", "ebs-volume-2"})).Return( + []volumes.VolumeProperties{ + {VolumeID: "ebs-volume-1", VolumeType: "gp2", Size: 150, Iops: 3000}, + {VolumeID: "ebs-volume-2", VolumeType: "gp2", Size: 150, Iops: 4000}, + }, nil) + + // expect only gp2 volume to be modified + resizer.EXPECT().ModifyVolume(gomock.Eq("ebs-volume-1"), gomock.Nil(), gomock.Eq(aws.Int64(177)), gomock.Nil(), gomock.Nil()).Return(nil) + resizer.EXPECT().ModifyVolume(gomock.Eq("ebs-volume-2"), gomock.Nil(), gomock.Eq(aws.Int64(177)), gomock.Nil(), gomock.Nil()).Return(nil) + + cluster.VolumeResizer = resizer + cluster.syncVolumes() +} diff --git a/pkg/util/volumes/ebs.go b/pkg/util/volumes/ebs.go index 17016fb09..8f998b4cb 100644 --- a/pkg/util/volumes/ebs.go +++ b/pkg/util/volumes/ebs.go @@ -141,18 +141,9 @@ func (r *EBSVolumeResizer) ResizeVolume(volumeID string, newSize int64) error { } // ModifyVolume Modify EBS volume -func (r *EBSVolumeResizer) ModifyVolume(volumeID string, newType string, newSize int64, iops int64, throughput int64) error { +func (r *EBSVolumeResizer) ModifyVolume(volumeID string, newType *string, newSize *int64, iops *int64, throughput *int64) error { /* first check if the volume is already of a requested size */ - volumeOutput, err := r.connection.DescribeVolumes(&ec2.DescribeVolumesInput{VolumeIds: []*string{&volumeID}}) - if err != nil { - return fmt.Errorf("could not get information about the volume: %v", err) - } - vol := volumeOutput.Volumes[0] - if *vol.VolumeId != volumeID { - return fmt.Errorf("describe volume %q returned information about a non-matching volume %q", volumeID, *vol.VolumeId) - } - - input := ec2.ModifyVolumeInput{Size: &newSize, VolumeId: &volumeID, VolumeType: &newType, Iops: &iops, Throughput: &throughput} + input := ec2.ModifyVolumeInput{Size: newSize, VolumeId: &volumeID, VolumeType: newType, Iops: iops, Throughput: throughput} output, err := r.connection.ModifyVolume(&input) if err != nil { return fmt.Errorf("could not modify persistent volume: %v", err) diff --git a/pkg/util/volumes/volumes.go b/pkg/util/volumes/volumes.go index 9b44c0d00..556729dc4 100644 --- a/pkg/util/volumes/volumes.go +++ b/pkg/util/volumes/volumes.go @@ -21,7 +21,7 @@ type VolumeResizer interface { GetProviderVolumeID(pv *v1.PersistentVolume) (string, error) ExtractVolumeID(volumeID string) (string, error) ResizeVolume(providerVolumeID string, newSize int64) error - ModifyVolume(providerVolumeID string, newType string, newSize int64, iops int64, throughput int64) error + ModifyVolume(providerVolumeID string, newType *string, newSize *int64, iops *int64, throughput *int64) error DisconnectFromProvider() error DescribeVolumes(providerVolumesID []string) ([]VolumeProperties, error) } From 5ecb7b42e0f84c0fcbf48ae1b6d5c32af49061ec Mon Sep 17 00:00:00 2001 From: Sergey Dudoladov Date: Mon, 25 Jan 2021 17:00:14 +0100 Subject: [PATCH 24/35] persist modified go.sum (#1329) Co-authored-by: Sergey Dudoladov --- go.sum | 4 ---- 1 file changed, 4 deletions(-) diff --git a/go.sum b/go.sum index fa8d2b135..64434e2e0 100644 --- a/go.sum +++ b/go.sum @@ -35,8 +35,6 @@ github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbt github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= -github.com/Storytel/gomock-matchers v1.2.0 h1:VPsbL6c/9/eCa4rH13LOEXPsIsnA1z+INamGIx1lWQo= -github.com/Storytel/gomock-matchers v1.2.0/go.mod h1:7HEuwyU/eq/W3mrSqPSYETGXiTyU2um0Rrb+dh5KmKM= github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -184,7 +182,6 @@ github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7 h1:5ZkaAPbicIKTF github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.1-0.20190311213431-837231f7bb37/go.mod h1:L3bP22mxdfCUHSUVMs+SPJMx55FrxQew7MSXT11Q86g= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= @@ -528,7 +525,6 @@ golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190221204921-83362c3779f5/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= From ac2a00c45eadf1d99775b2ed77ebdf36f104116a Mon Sep 17 00:00:00 2001 From: Felix Kunde Date: Mon, 25 Jan 2021 18:23:29 +0100 Subject: [PATCH 25/35] set allowPrivilegeEscalation for deployment templates (#1328) * set allowPrivilegeEscalation for deployment templates * securityContext of container, not pod * aligning * default service account for pooler --- charts/postgres-operator/templates/deployment.yaml | 2 ++ charts/postgres-operator/values-crd.yaml | 14 ++++++++++---- charts/postgres-operator/values.yaml | 14 ++++++++++---- manifests/complete-postgres-manifest.yaml | 2 +- manifests/postgres-operator.yaml | 1 + pkg/cluster/connection_pooler.go | 4 +++- 6 files changed, 27 insertions(+), 10 deletions(-) diff --git a/charts/postgres-operator/templates/deployment.yaml b/charts/postgres-operator/templates/deployment.yaml index 9841bf1bc..89500ae94 100644 --- a/charts/postgres-operator/templates/deployment.yaml +++ b/charts/postgres-operator/templates/deployment.yaml @@ -54,6 +54,8 @@ spec: {{- end }} resources: {{ toYaml .Values.resources | indent 10 }} + securityContext: +{{ toYaml .Values.securityContext | indent 10 }} {{- if .Values.imagePullSecrets }} imagePullSecrets: {{ toYaml .Values.imagePullSecrets | indent 8 }} diff --git a/charts/postgres-operator/values-crd.yaml b/charts/postgres-operator/values-crd.yaml index 3593dd276..f3115dc8e 100644 --- a/charts/postgres-operator/values-crd.yaml +++ b/charts/postgres-operator/values-crd.yaml @@ -359,18 +359,24 @@ resources: cpu: 100m memory: 250Mi +securityContext: + runAsUser: 1000 + runAsNonRoot: true + readOnlyRootFilesystem: true + allowPrivilegeEscalation: false + # Affinity for pod assignment # Ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity affinity: {} -# Tolerations for pod assignment -# Ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ -tolerations: [] - # Node labels for pod assignment # Ref: https://kubernetes.io/docs/user-guide/node-selection/ nodeSelector: {} +# Tolerations for pod assignment +# Ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ +tolerations: [] + controllerID: # Specifies whether a controller ID should be defined for the operator # Note, all postgres manifest must then contain the following annotation to be found by this operator diff --git a/charts/postgres-operator/values.yaml b/charts/postgres-operator/values.yaml index 15f13df7e..e8a330d4b 100644 --- a/charts/postgres-operator/values.yaml +++ b/charts/postgres-operator/values.yaml @@ -354,18 +354,24 @@ resources: cpu: 100m memory: 250Mi +securityContext: + runAsUser: 1000 + runAsNonRoot: true + readOnlyRootFilesystem: true + allowPrivilegeEscalation: false + # Affinity for pod assignment # Ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity affinity: {} -# Tolerations for pod assignment -# Ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ -tolerations: [] - # Node labels for pod assignment # Ref: https://kubernetes.io/docs/user-guide/node-selection/ nodeSelector: {} +# Tolerations for pod assignment +# Ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ +tolerations: [] + controllerID: # Specifies whether a controller ID should be defined for the operator # Note, all postgres manifest must then contain the following annotation to be found by this operator diff --git a/manifests/complete-postgres-manifest.yaml b/manifests/complete-postgres-manifest.yaml index f721f0ccb..9f2d19639 100644 --- a/manifests/complete-postgres-manifest.yaml +++ b/manifests/complete-postgres-manifest.yaml @@ -45,7 +45,7 @@ spec: size: 1Gi # storageClass: my-sc # iops: 1000 # for EBS gp3 - # throughput: 250 # in MB/s for EBS gp3 +# throughput: 250 # in MB/s for EBS gp3 additionalVolumes: - name: empty mountPath: /opt/empty diff --git a/manifests/postgres-operator.yaml b/manifests/postgres-operator.yaml index da4ca7fc6..a03959805 100644 --- a/manifests/postgres-operator.yaml +++ b/manifests/postgres-operator.yaml @@ -32,6 +32,7 @@ spec: runAsUser: 1000 runAsNonRoot: true readOnlyRootFilesystem: true + allowPrivilegeEscalation: false env: # provided additional ENV vars can overwrite individual config map entries - name: CONFIG_MAP_NAME diff --git a/pkg/cluster/connection_pooler.go b/pkg/cluster/connection_pooler.go index 2e3f04876..db4f1f56d 100644 --- a/pkg/cluster/connection_pooler.go +++ b/pkg/cluster/connection_pooler.go @@ -280,6 +280,9 @@ func (c *Cluster) generateConnectionPoolerPodTemplate(role PostgresRole) ( }, }, }, + SecurityContext: &v1.SecurityContext{ + AllowPrivilegeEscalation: util.False(), + }, } podTemplate := &v1.PodTemplateSpec{ @@ -289,7 +292,6 @@ func (c *Cluster) generateConnectionPoolerPodTemplate(role PostgresRole) ( Annotations: c.annotationsSet(c.generatePodAnnotations(spec)), }, Spec: v1.PodSpec{ - ServiceAccountName: c.OpConfig.PodServiceAccountName, TerminationGracePeriodSeconds: &gracePeriod, Containers: []v1.Container{poolerContainer}, // TODO: add tolerations to scheduler pooler on the same node From 43168ca62201d81a2c5d57b5b107b5481c0af0f7 Mon Sep 17 00:00:00 2001 From: Jan Mussler Date: Mon, 25 Jan 2021 20:28:37 +0100 Subject: [PATCH 26/35] Also sync volumes on updates. (#1330) --- pkg/cluster/cluster.go | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/pkg/cluster/cluster.go b/pkg/cluster/cluster.go index 42515a7c0..16d399865 100644 --- a/pkg/cluster/cluster.go +++ b/pkg/cluster/cluster.go @@ -659,20 +659,8 @@ func (c *Cluster) Update(oldSpec, newSpec *acidv1.Postgresql) error { } // Volume - if oldSpec.Spec.Size != newSpec.Spec.Size { - c.logVolumeChanges(oldSpec.Spec.Volume, newSpec.Spec.Volume) - c.logger.Debugf("syncing volumes using %q storage resize mode", c.OpConfig.StorageResizeMode) - if c.OpConfig.StorageResizeMode == "pvc" { - if err := c.syncVolumeClaims(); err != nil { - c.logger.Errorf("could not sync persistent volume claims: %v", err) - updateFailed = true - } - } else if c.OpConfig.StorageResizeMode == "ebs" { - if err := c.syncVolumes(); err != nil { - c.logger.Errorf("could not sync persistent volumes: %v", err) - updateFailed = true - } - } + if c.OpConfig.StorageResizeMode != "off" { + c.syncVolumes() } else { c.logger.Infof("Storage resize is disabled (storage_resize_mode is off). Skipping volume sync.") } From 9d5841cdaaefba9aba1a12112ebca9b7dee00eb4 Mon Sep 17 00:00:00 2001 From: Victor Login Date: Wed, 27 Jan 2021 12:11:44 +0300 Subject: [PATCH 27/35] Update values.yaml (#1333) --- charts/postgres-operator-ui/values.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/charts/postgres-operator-ui/values.yaml b/charts/postgres-operator-ui/values.yaml index dea5007c9..2aef84f1c 100644 --- a/charts/postgres-operator-ui/values.yaml +++ b/charts/postgres-operator-ui/values.yaml @@ -52,7 +52,7 @@ service: port: "80" # If the type of the service is NodePort a port can be specified using the nodePort field # If the nodePort field is not specified, or if it has no value, then a random port is used - # notePort: 32521 + # nodePort: 32521 # configure UI ingress. If needed: "enabled: true" ingress: From d488ae10a04995a533d13c33a2e6da7106fa70b8 Mon Sep 17 00:00:00 2001 From: Jan Mussler Date: Fri, 29 Jan 2021 11:12:08 +0100 Subject: [PATCH 28/35] Min 2 zalando approvers. (#1338) --- .zappr.yaml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.zappr.yaml b/.zappr.yaml index 999c121d7..00be0052e 100644 --- a/.zappr.yaml +++ b/.zappr.yaml @@ -3,3 +3,11 @@ X-Zalando-Team: "acid" # type should be one of [code, doc, config, tools, secrets] # code will be the default value, if X-Zalando-Type is not found in .zappr.yml X-Zalando-Type: code + +approvals: + groups: + zalando: + minimum: 2 + from: + orgs: + - zalando \ No newline at end of file From 12ad8c91fa0fcd1f82984b0da33b5e4f4d8c0ba3 Mon Sep 17 00:00:00 2001 From: Felix Kunde Date: Fri, 29 Jan 2021 14:54:48 +0100 Subject: [PATCH 29/35] configurable container capabilities (#1336) * configurable container capabilities * revert change on TestTLS * fix e2e test * minor fix --- .../crds/operatorconfigurations.yaml | 4 ++ charts/postgres-operator/values-crd.yaml | 4 ++ charts/postgres-operator/values.yaml | 3 + docs/reference/operator_parameters.md | 6 ++ e2e/tests/k8s_api.py | 8 +++ e2e/tests/test_e2e.py | 19 ++++++ manifests/configmap.yaml | 1 + manifests/operatorconfiguration.crd.yaml | 4 ++ ...gresql-operator-default-configuration.yaml | 2 + pkg/apis/acid.zalan.do/v1/crds.go | 8 +++ .../v1/operator_configuration_type.go | 1 + .../acid.zalan.do/v1/zz_generated.deepcopy.go | 5 ++ pkg/cluster/k8sres.go | 17 +++++ pkg/cluster/k8sres_test.go | 39 +++++++++++ pkg/controller/operator_config.go | 1 + pkg/util/config/config.go | 65 ++++++++++--------- 16 files changed, 155 insertions(+), 32 deletions(-) diff --git a/charts/postgres-operator/crds/operatorconfigurations.yaml b/charts/postgres-operator/crds/operatorconfigurations.yaml index a360da0c6..ef9b2c84d 100644 --- a/charts/postgres-operator/crds/operatorconfigurations.yaml +++ b/charts/postgres-operator/crds/operatorconfigurations.yaml @@ -130,6 +130,10 @@ spec: kubernetes: type: object properties: + additional_pod_capabilities: + type: array + items: + type: string cluster_domain: type: string default: "cluster.local" diff --git a/charts/postgres-operator/values-crd.yaml b/charts/postgres-operator/values-crd.yaml index f3115dc8e..42af903cd 100644 --- a/charts/postgres-operator/values-crd.yaml +++ b/charts/postgres-operator/values-crd.yaml @@ -59,6 +59,10 @@ configUsers: super_username: postgres configKubernetes: + # list of additional capabilities for postgres container + # additional_pod_capabilities: + # - "SYS_NICE" + # default DNS domain of K8s cluster where operator is running cluster_domain: cluster.local # additional labels assigned to the cluster objects diff --git a/charts/postgres-operator/values.yaml b/charts/postgres-operator/values.yaml index e8a330d4b..c46e21e1f 100644 --- a/charts/postgres-operator/values.yaml +++ b/charts/postgres-operator/values.yaml @@ -61,6 +61,9 @@ configUsers: super_username: postgres configKubernetes: + # list of additional capabilities for postgres container + # additional_pod_capabilities: "SYS_NICE" + # default DNS domain of K8s cluster where operator is running cluster_domain: cluster.local # additional labels assigned to the cluster objects diff --git a/docs/reference/operator_parameters.md b/docs/reference/operator_parameters.md index 5c5850505..54d13ffc2 100644 --- a/docs/reference/operator_parameters.md +++ b/docs/reference/operator_parameters.md @@ -351,6 +351,12 @@ configuration they are grouped under the `kubernetes` key. used for AWS volume resizing and not required if you don't need that capability. The default is `false`. +* **additional_pod_capabilities** + list of additional capabilities to be added to the postgres container's + SecurityContext (e.g. SYS_NICE etc.). Please, make sure first that the + PodSecruityPolicy allows the capabilities listed here. Otherwise, the + container will not start. The default is empty. + * **master_pod_move_timeout** The period of time to wait for the success of migration of master pods from an unschedulable node. The migration includes Patroni switchovers to diff --git a/e2e/tests/k8s_api.py b/e2e/tests/k8s_api.py index 95e1dc9ad..21e2a16e9 100644 --- a/e2e/tests/k8s_api.py +++ b/e2e/tests/k8s_api.py @@ -182,6 +182,10 @@ class K8s: pods = self.api.core_v1.list_namespaced_pod(namespace, label_selector=labels).items return len(list(filter(lambda x: x.status.phase == 'Running', pods))) + def count_pods_with_container_capabilities(self, capabilities, labels, namespace='default'): + pods = self.api.core_v1.list_namespaced_pod(namespace, label_selector=labels).items + return len(list(filter(lambda x: x.spec.containers[0].security_context.capabilities.add == capabilities, pods))) + def wait_for_pod_failover(self, failover_targets, labels, namespace='default'): pod_phase = 'Failing over' new_pod_node = '' @@ -433,6 +437,10 @@ class K8sBase: pods = self.api.core_v1.list_namespaced_pod(namespace, label_selector=labels).items return len(list(filter(lambda x: x.status.phase == 'Running', pods))) + def count_pods_with_container_capabilities(self, capabilities, labels, namespace='default'): + pods = self.api.core_v1.list_namespaced_pod(namespace, label_selector=labels).items + return len(list(filter(lambda x: x.spec.containers[0].security_context.capabilities.add == capabilities, pods))) + def wait_for_pod_failover(self, failover_targets, labels, namespace='default'): pod_phase = 'Failing over' new_pod_node = '' diff --git a/e2e/tests/test_e2e.py b/e2e/tests/test_e2e.py index ecc0b2327..87bed3baa 100644 --- a/e2e/tests/test_e2e.py +++ b/e2e/tests/test_e2e.py @@ -155,6 +155,25 @@ class EndToEndTestCase(unittest.TestCase): print('Operator log: {}'.format(k8s.get_operator_log())) raise + @timeout_decorator.timeout(TEST_TIMEOUT_SEC) + def test_additional_pod_capabilities(self): + ''' + Extend postgres container capabilities + ''' + cluster_label = 'application=spilo,cluster-name=acid-minimal-cluster' + capabilities = ["SYS_NICE","CHOWN"] + patch_capabilities = { + "data": { + "additional_pod_capabilities": ','.join(capabilities), + }, + } + self.k8s.update_config(patch_capabilities) + self.eventuallyEqual(lambda: self.k8s.get_operator_state(), {"0": "idle"}, + "Operator does not get in sync") + + self.eventuallyEqual(lambda: self.k8s.count_pods_with_container_capabilities(capabilities, cluster_label), + 2, "Container capabilities not updated") + @timeout_decorator.timeout(TEST_TIMEOUT_SEC) def test_overwrite_pooler_deployment(self): self.k8s.create_with_kubectl("manifests/minimal-fake-pooler-deployment.yaml") diff --git a/manifests/configmap.yaml b/manifests/configmap.yaml index 3788d8b32..f1bde6811 100644 --- a/manifests/configmap.yaml +++ b/manifests/configmap.yaml @@ -3,6 +3,7 @@ kind: ConfigMap metadata: name: postgres-operator data: + # additional_pod_capabilities: "SYS_NICE" # additional_secret_mount: "some-secret-name" # additional_secret_mount_path: "/some/dir" api_port: "8080" diff --git a/manifests/operatorconfiguration.crd.yaml b/manifests/operatorconfiguration.crd.yaml index 7add1b8c6..7388a765b 100644 --- a/manifests/operatorconfiguration.crd.yaml +++ b/manifests/operatorconfiguration.crd.yaml @@ -126,6 +126,10 @@ spec: kubernetes: type: object properties: + additional_pod_capabilities: + type: array + items: + type: string cluster_domain: type: string default: "cluster.local" diff --git a/manifests/postgresql-operator-default-configuration.yaml b/manifests/postgresql-operator-default-configuration.yaml index 96394976d..18680fbb0 100644 --- a/manifests/postgresql-operator-default-configuration.yaml +++ b/manifests/postgresql-operator-default-configuration.yaml @@ -26,6 +26,8 @@ configuration: replication_username: standby super_username: postgres kubernetes: + # additional_pod_capabilities: + # - "SYS_NICE" cluster_domain: cluster.local cluster_labels: application: spilo diff --git a/pkg/apis/acid.zalan.do/v1/crds.go b/pkg/apis/acid.zalan.do/v1/crds.go index 02d40342f..3c4bc315a 100644 --- a/pkg/apis/acid.zalan.do/v1/crds.go +++ b/pkg/apis/acid.zalan.do/v1/crds.go @@ -968,6 +968,14 @@ var OperatorConfigCRDResourceValidation = apiextv1.CustomResourceValidation{ "kubernetes": { Type: "object", Properties: map[string]apiextv1.JSONSchemaProps{ + "additional_pod_capabilities": { + Type: "array", + Items: &apiextv1.JSONSchemaPropsOrArray{ + Schema: &apiextv1.JSONSchemaProps{ + Type: "string", + }, + }, + }, "cluster_domain": { Type: "string", }, diff --git a/pkg/apis/acid.zalan.do/v1/operator_configuration_type.go b/pkg/apis/acid.zalan.do/v1/operator_configuration_type.go index b55bfa492..cddaa9dd4 100644 --- a/pkg/apis/acid.zalan.do/v1/operator_configuration_type.go +++ b/pkg/apis/acid.zalan.do/v1/operator_configuration_type.go @@ -52,6 +52,7 @@ type KubernetesMetaConfiguration struct { SpiloRunAsUser *int64 `json:"spilo_runasuser,omitempty"` SpiloRunAsGroup *int64 `json:"spilo_runasgroup,omitempty"` SpiloFSGroup *int64 `json:"spilo_fsgroup,omitempty"` + AdditionalPodCapabilities []string `json:"additional_pod_capabilities,omitempty"` WatchedNamespace string `json:"watched_namespace,omitempty"` PDBNameFormat config.StringTemplate `json:"pdb_name_format,omitempty"` EnablePodDisruptionBudget *bool `json:"enable_pod_disruption_budget,omitempty"` diff --git a/pkg/apis/acid.zalan.do/v1/zz_generated.deepcopy.go b/pkg/apis/acid.zalan.do/v1/zz_generated.deepcopy.go index 4bcbd2f5e..81f8a76b5 100644 --- a/pkg/apis/acid.zalan.do/v1/zz_generated.deepcopy.go +++ b/pkg/apis/acid.zalan.do/v1/zz_generated.deepcopy.go @@ -162,6 +162,11 @@ func (in *KubernetesMetaConfiguration) DeepCopyInto(out *KubernetesMetaConfigura *out = new(int64) **out = **in } + if in.AdditionalPodCapabilities != nil { + in, out := &in.AdditionalPodCapabilities, &out.AdditionalPodCapabilities + *out = make([]string, len(*in)) + copy(*out, *in) + } if in.EnablePodDisruptionBudget != nil { in, out := &in.EnablePodDisruptionBudget, &out.EnablePodDisruptionBudget *out = new(bool) diff --git a/pkg/cluster/k8sres.go b/pkg/cluster/k8sres.go index 83098b8a9..56500bb29 100644 --- a/pkg/cluster/k8sres.go +++ b/pkg/cluster/k8sres.go @@ -320,6 +320,19 @@ func getLocalAndBoostrapPostgreSQLParameters(parameters map[string]string) (loca return } +func generateCapabilities(capabilities []string) v1.Capabilities { + if len(capabilities) > 1 { + additionalCapabilities := []v1.Capability{} + for _, capability := range capabilities { + additionalCapabilities = append(additionalCapabilities, v1.Capability(strings.ToUpper(capability))) + } + return v1.Capabilities{ + Add: additionalCapabilities, + } + } + return v1.Capabilities{} +} + func nodeAffinity(nodeReadinessLabel map[string]string, nodeAffinity *v1.NodeAffinity) *v1.Affinity { if len(nodeReadinessLabel) == 0 && nodeAffinity == nil { return nil @@ -430,6 +443,7 @@ func generateContainer( envVars []v1.EnvVar, volumeMounts []v1.VolumeMount, privilegedMode bool, + additionalPodCapabilities v1.Capabilities, ) *v1.Container { return &v1.Container{ Name: name, @@ -456,6 +470,7 @@ func generateContainer( AllowPrivilegeEscalation: &privilegedMode, Privileged: &privilegedMode, ReadOnlyRootFilesystem: util.False(), + Capabilities: &additionalPodCapabilities, }, } } @@ -1148,6 +1163,7 @@ func (c *Cluster) generateStatefulSet(spec *acidv1.PostgresSpec) (*appsv1.Statef deduplicateEnvVars(spiloEnvVars, c.containerName(), c.logger), volumeMounts, c.OpConfig.Resources.SpiloPrivileged, + generateCapabilities(c.OpConfig.AdditionalPodCapabilities), ) // generate container specs for sidecars specified in the cluster manifest @@ -1901,6 +1917,7 @@ func (c *Cluster) generateLogicalBackupJob() (*batchv1beta1.CronJob, error) { envVars, []v1.VolumeMount{}, c.OpConfig.SpiloPrivileged, // use same value as for normal DB pods + v1.Capabilities{}, ) labels := map[string]string{ diff --git a/pkg/cluster/k8sres_test.go b/pkg/cluster/k8sres_test.go index e880fcc3b..6034db214 100644 --- a/pkg/cluster/k8sres_test.go +++ b/pkg/cluster/k8sres_test.go @@ -1489,3 +1489,42 @@ func TestGenerateService(t *testing.T) { assert.Equal(t, v1.ServiceExternalTrafficPolicyTypeLocal, service.Spec.ExternalTrafficPolicy) } + +func TestGenerateCapabilities(t *testing.T) { + + testName := "TestGenerateCapabilities" + tests := []struct { + subTest string + configured []string + capabilities v1.Capabilities + err error + }{ + { + subTest: "no capabilities", + configured: nil, + capabilities: v1.Capabilities{}, + err: fmt.Errorf("could not parse capabilities configuration of nil"), + }, + { + subTest: "empty capabilities", + configured: []string{}, + capabilities: v1.Capabilities{}, + err: fmt.Errorf("could not parse empty capabilities configuration"), + }, + { + subTest: "configured capabilities", + configured: []string{"SYS_NICE", "CHOWN"}, + capabilities: v1.Capabilities{ + Add: []v1.Capability{"SYS_NICE", "CHOWN"}, + }, + err: fmt.Errorf("could not parse empty capabilities configuration"), + }, + } + for _, tt := range tests { + caps := generateCapabilities(tt.configured) + if !reflect.DeepEqual(caps, tt.capabilities) { + t.Errorf("%s %s: expected `%v` but got `%v`", + testName, tt.subTest, tt.capabilities, caps) + } + } +} diff --git a/pkg/controller/operator_config.go b/pkg/controller/operator_config.go index 16fb05004..6ef7a2f42 100644 --- a/pkg/controller/operator_config.go +++ b/pkg/controller/operator_config.go @@ -66,6 +66,7 @@ func (c *Controller) importConfigurationFromCRD(fromCRD *acidv1.OperatorConfigur result.SpiloRunAsUser = fromCRD.Kubernetes.SpiloRunAsUser result.SpiloRunAsGroup = fromCRD.Kubernetes.SpiloRunAsGroup result.SpiloFSGroup = fromCRD.Kubernetes.SpiloFSGroup + result.AdditionalPodCapabilities = fromCRD.Kubernetes.AdditionalPodCapabilities result.ClusterDomain = util.Coalesce(fromCRD.Kubernetes.ClusterDomain, "cluster.local") result.WatchedNamespace = fromCRD.Kubernetes.WatchedNamespace result.PDBNameFormat = fromCRD.Kubernetes.PDBNameFormat diff --git a/pkg/util/config/config.go b/pkg/util/config/config.go index 1d8e37bd2..6bc5a6a28 100644 --- a/pkg/util/config/config.go +++ b/pkg/util/config/config.go @@ -23,38 +23,39 @@ type CRD struct { // Resources describes kubernetes resource specific configuration parameters type Resources struct { - ResourceCheckInterval time.Duration `name:"resource_check_interval" default:"3s"` - ResourceCheckTimeout time.Duration `name:"resource_check_timeout" default:"10m"` - PodLabelWaitTimeout time.Duration `name:"pod_label_wait_timeout" default:"10m"` - PodDeletionWaitTimeout time.Duration `name:"pod_deletion_wait_timeout" default:"10m"` - PodTerminateGracePeriod time.Duration `name:"pod_terminate_grace_period" default:"5m"` - SpiloRunAsUser *int64 `json:"spilo_runasuser,omitempty"` - SpiloRunAsGroup *int64 `json:"spilo_runasgroup,omitempty"` - SpiloFSGroup *int64 `name:"spilo_fsgroup"` - PodPriorityClassName string `name:"pod_priority_class_name"` - ClusterDomain string `name:"cluster_domain" default:"cluster.local"` - SpiloPrivileged bool `name:"spilo_privileged" default:"false"` - ClusterLabels map[string]string `name:"cluster_labels" default:"application:spilo"` - InheritedLabels []string `name:"inherited_labels" default:""` - InheritedAnnotations []string `name:"inherited_annotations" default:""` - DownscalerAnnotations []string `name:"downscaler_annotations"` - ClusterNameLabel string `name:"cluster_name_label" default:"cluster-name"` - DeleteAnnotationDateKey string `name:"delete_annotation_date_key"` - DeleteAnnotationNameKey string `name:"delete_annotation_name_key"` - PodRoleLabel string `name:"pod_role_label" default:"spilo-role"` - PodToleration map[string]string `name:"toleration" default:""` - DefaultCPURequest string `name:"default_cpu_request" default:"100m"` - DefaultMemoryRequest string `name:"default_memory_request" default:"100Mi"` - DefaultCPULimit string `name:"default_cpu_limit" default:"1"` - DefaultMemoryLimit string `name:"default_memory_limit" default:"500Mi"` - MinCPULimit string `name:"min_cpu_limit" default:"250m"` - MinMemoryLimit string `name:"min_memory_limit" default:"250Mi"` - PodEnvironmentConfigMap spec.NamespacedName `name:"pod_environment_configmap"` - PodEnvironmentSecret string `name:"pod_environment_secret"` - NodeReadinessLabel map[string]string `name:"node_readiness_label" default:""` - MaxInstances int32 `name:"max_instances" default:"-1"` - MinInstances int32 `name:"min_instances" default:"-1"` - ShmVolume *bool `name:"enable_shm_volume" default:"true"` + ResourceCheckInterval time.Duration `name:"resource_check_interval" default:"3s"` + ResourceCheckTimeout time.Duration `name:"resource_check_timeout" default:"10m"` + PodLabelWaitTimeout time.Duration `name:"pod_label_wait_timeout" default:"10m"` + PodDeletionWaitTimeout time.Duration `name:"pod_deletion_wait_timeout" default:"10m"` + PodTerminateGracePeriod time.Duration `name:"pod_terminate_grace_period" default:"5m"` + SpiloRunAsUser *int64 `json:"spilo_runasuser,omitempty"` + SpiloRunAsGroup *int64 `json:"spilo_runasgroup,omitempty"` + SpiloFSGroup *int64 `name:"spilo_fsgroup"` + PodPriorityClassName string `name:"pod_priority_class_name"` + ClusterDomain string `name:"cluster_domain" default:"cluster.local"` + SpiloPrivileged bool `name:"spilo_privileged" default:"false"` + AdditionalPodCapabilities []string `name:"additional_pod_capabilities" default:""` + ClusterLabels map[string]string `name:"cluster_labels" default:"application:spilo"` + InheritedLabels []string `name:"inherited_labels" default:""` + InheritedAnnotations []string `name:"inherited_annotations" default:""` + DownscalerAnnotations []string `name:"downscaler_annotations"` + ClusterNameLabel string `name:"cluster_name_label" default:"cluster-name"` + DeleteAnnotationDateKey string `name:"delete_annotation_date_key"` + DeleteAnnotationNameKey string `name:"delete_annotation_name_key"` + PodRoleLabel string `name:"pod_role_label" default:"spilo-role"` + PodToleration map[string]string `name:"toleration" default:""` + DefaultCPURequest string `name:"default_cpu_request" default:"100m"` + DefaultMemoryRequest string `name:"default_memory_request" default:"100Mi"` + DefaultCPULimit string `name:"default_cpu_limit" default:"1"` + DefaultMemoryLimit string `name:"default_memory_limit" default:"500Mi"` + MinCPULimit string `name:"min_cpu_limit" default:"250m"` + MinMemoryLimit string `name:"min_memory_limit" default:"250Mi"` + PodEnvironmentConfigMap spec.NamespacedName `name:"pod_environment_configmap"` + PodEnvironmentSecret string `name:"pod_environment_secret"` + NodeReadinessLabel map[string]string `name:"node_readiness_label" default:""` + MaxInstances int32 `name:"max_instances" default:"-1"` + MinInstances int32 `name:"min_instances" default:"-1"` + ShmVolume *bool `name:"enable_shm_volume" default:"true"` } type InfrastructureRole struct { From 0cce565b651000639f6758444c12b3397e68c2cf Mon Sep 17 00:00:00 2001 From: Felix Kunde Date: Fri, 29 Jan 2021 16:10:27 +0100 Subject: [PATCH 30/35] fix when adding only one capability (#1339) * fix when adding only one capability * fix error messages in unit test --- pkg/cluster/k8sres.go | 2 +- pkg/cluster/k8sres_test.go | 10 +++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/pkg/cluster/k8sres.go b/pkg/cluster/k8sres.go index 56500bb29..b1a8adb0b 100644 --- a/pkg/cluster/k8sres.go +++ b/pkg/cluster/k8sres.go @@ -321,7 +321,7 @@ func getLocalAndBoostrapPostgreSQLParameters(parameters map[string]string) (loca } func generateCapabilities(capabilities []string) v1.Capabilities { - if len(capabilities) > 1 { + if len(capabilities) > 0 { additionalCapabilities := []v1.Capability{} for _, capability := range capabilities { additionalCapabilities = append(additionalCapabilities, v1.Capability(strings.ToUpper(capability))) diff --git a/pkg/cluster/k8sres_test.go b/pkg/cluster/k8sres_test.go index 6034db214..efed071d7 100644 --- a/pkg/cluster/k8sres_test.go +++ b/pkg/cluster/k8sres_test.go @@ -1511,13 +1511,21 @@ func TestGenerateCapabilities(t *testing.T) { capabilities: v1.Capabilities{}, err: fmt.Errorf("could not parse empty capabilities configuration"), }, + { + subTest: "configured capability", + configured: []string{"SYS_NICE"}, + capabilities: v1.Capabilities{ + Add: []v1.Capability{"SYS_NICE"}, + }, + err: fmt.Errorf("could not generate one configured capability"), + }, { subTest: "configured capabilities", configured: []string{"SYS_NICE", "CHOWN"}, capabilities: v1.Capabilities{ Add: []v1.Capability{"SYS_NICE", "CHOWN"}, }, - err: fmt.Errorf("could not parse empty capabilities configuration"), + err: fmt.Errorf("could not generate multiple configured capabilities"), }, } for _, tt := range tests { From 2c3cd3ae02fe032d8d1036161318bb5f6c0e7056 Mon Sep 17 00:00:00 2001 From: Aaron Peschel Date: Wed, 3 Feb 2021 05:41:29 -0800 Subject: [PATCH 31/35] Fix CI Pipeline (#1345) This commit attempts to fix the CI issues by upgrading the checkout action to v2. This fix is documented by the coverallsapp upstream. --- .github/workflows/run_tests.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run_tests.yaml b/.github/workflows/run_tests.yaml index d66514a5c..de14267b4 100644 --- a/.github/workflows/run_tests.yaml +++ b/.github/workflows/run_tests.yaml @@ -11,7 +11,7 @@ jobs: name: Unit tests and coverage runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v2 - uses: actions/setup-go@v2 with: go-version: "^1.15.6" From 6aeb92f024abfc543a1b49cae37e943c99e0ee1b Mon Sep 17 00:00:00 2001 From: zvier Date: Tue, 9 Feb 2021 16:35:24 +0800 Subject: [PATCH 32/35] code optimization (#1350) * pre-allocate cap for slice structure * if clause is no need because of range, and kubelet also use range method to get each capability so there is no side-effect Signed-off-by: Jeff Zvier --- pkg/cluster/k8sres.go | 15 ++++++--------- pkg/cluster/k8sres_test.go | 4 ++-- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/pkg/cluster/k8sres.go b/pkg/cluster/k8sres.go index b1a8adb0b..3e199e935 100644 --- a/pkg/cluster/k8sres.go +++ b/pkg/cluster/k8sres.go @@ -321,16 +321,13 @@ func getLocalAndBoostrapPostgreSQLParameters(parameters map[string]string) (loca } func generateCapabilities(capabilities []string) v1.Capabilities { - if len(capabilities) > 0 { - additionalCapabilities := []v1.Capability{} - for _, capability := range capabilities { - additionalCapabilities = append(additionalCapabilities, v1.Capability(strings.ToUpper(capability))) - } - return v1.Capabilities{ - Add: additionalCapabilities, - } + additionalCapabilities := make([]v1.Capability, 0, len(capabilities)) + for _, capability := range capabilities { + additionalCapabilities = append(additionalCapabilities, v1.Capability(strings.ToUpper(capability))) + } + return v1.Capabilities{ + Add: additionalCapabilities, } - return v1.Capabilities{} } func nodeAffinity(nodeReadinessLabel map[string]string, nodeAffinity *v1.NodeAffinity) *v1.Affinity { diff --git a/pkg/cluster/k8sres_test.go b/pkg/cluster/k8sres_test.go index efed071d7..b10123782 100644 --- a/pkg/cluster/k8sres_test.go +++ b/pkg/cluster/k8sres_test.go @@ -1502,13 +1502,13 @@ func TestGenerateCapabilities(t *testing.T) { { subTest: "no capabilities", configured: nil, - capabilities: v1.Capabilities{}, + capabilities: v1.Capabilities{Add: []v1.Capability{}}, err: fmt.Errorf("could not parse capabilities configuration of nil"), }, { subTest: "empty capabilities", configured: []string{}, - capabilities: v1.Capabilities{}, + capabilities: v1.Capabilities{Add: []v1.Capability{}}, err: fmt.Errorf("could not parse empty capabilities configuration"), }, { From 4d2b51de415a20e49894c218d0cd49dddfe07551 Mon Sep 17 00:00:00 2001 From: Felix Kunde Date: Wed, 10 Feb 2021 11:24:24 +0100 Subject: [PATCH 33/35] upgrade pip to latest version to avoid broken deps (#1357) --- docker/logical-backup/Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/docker/logical-backup/Dockerfile b/docker/logical-backup/Dockerfile index b84ea2b22..62bd5ce8c 100644 --- a/docker/logical-backup/Dockerfile +++ b/docker/logical-backup/Dockerfile @@ -15,6 +15,7 @@ RUN apt-get update \ gnupg \ gcc \ libffi-dev \ + && pip3 install --upgrade pip \ && pip3 install --no-cache-dir awscli --upgrade \ && pip3 install --no-cache-dir gsutil --upgrade \ && echo "deb http://apt.postgresql.org/pub/repos/apt/ $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list \ From c1c0d5faa144b23421a20d0bc72d1caa451cf631 Mon Sep 17 00:00:00 2001 From: Tommaso Pozzetti <34100051+tommasopozzetti@users.noreply.github.com> Date: Fri, 12 Feb 2021 04:04:23 -0600 Subject: [PATCH 34/35] Fix operator configs runasuser and runasgroup (#1361) This commit fixes a typo in the operator's configuration that would lead to the configs spilo_runasuser and spilo_runasgroup to be ignored. --- pkg/util/config/config.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/util/config/config.go b/pkg/util/config/config.go index 6bc5a6a28..4f4940567 100644 --- a/pkg/util/config/config.go +++ b/pkg/util/config/config.go @@ -28,8 +28,8 @@ type Resources struct { PodLabelWaitTimeout time.Duration `name:"pod_label_wait_timeout" default:"10m"` PodDeletionWaitTimeout time.Duration `name:"pod_deletion_wait_timeout" default:"10m"` PodTerminateGracePeriod time.Duration `name:"pod_terminate_grace_period" default:"5m"` - SpiloRunAsUser *int64 `json:"spilo_runasuser,omitempty"` - SpiloRunAsGroup *int64 `json:"spilo_runasgroup,omitempty"` + SpiloRunAsUser *int64 `name:"spilo_runasuser,omitempty"` + SpiloRunAsGroup *int64 `name:"spilo_runasgroup,omitempty"` SpiloFSGroup *int64 `name:"spilo_fsgroup"` PodPriorityClassName string `name:"pod_priority_class_name"` ClusterDomain string `name:"cluster_domain" default:"cluster.local"` From 772f0ca77188c8904277a0c30f4380af0b3f5462 Mon Sep 17 00:00:00 2001 From: Jan Mussler Date: Fri, 12 Feb 2021 17:36:11 +0100 Subject: [PATCH 35/35] Fix volume sync order. (#1340) --- pkg/cluster/sync.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/pkg/cluster/sync.go b/pkg/cluster/sync.go index 5c0f6ce84..0144857b9 100644 --- a/pkg/cluster/sync.go +++ b/pkg/cluster/sync.go @@ -53,6 +53,11 @@ func (c *Cluster) Sync(newSpec *acidv1.Postgresql) error { return err } + // sync volume may already transition volumes to gp3, if iops/throughput or type is specified + if err = c.syncVolumes(); err != nil { + return err + } + if c.OpConfig.EnableEBSGp3Migration { err = c.executeEBSMigration() if nil != err { @@ -60,10 +65,6 @@ func (c *Cluster) Sync(newSpec *acidv1.Postgresql) error { } } - if err = c.syncVolumes(); err != nil { - return err - } - if err = c.enforceMinResourceLimits(&c.Spec); err != nil { err = fmt.Errorf("could not enforce minimum resource limits: %v", err) return err