From 4f3d7a8df59518797c43908d71dc48fec280fb43 Mon Sep 17 00:00:00 2001 From: Hiranmoy Das Chowdhury Date: Mon, 18 Nov 2024 18:39:01 +0600 Subject: [PATCH 01/29] Signed-off-by: Hiranmoy Das Chowdhury --- docs/guides/pgbouncer/concepts/autoscaler.md | 73 ++++ docs/guides/pgbouncer/concepts/opsrequest.md | 330 ++++++++++++++++++ docs/guides/pgbouncer/sync-users/_index.md | 10 + .../sync-users/sync-users-pgbouncer.md | 195 +++++++++++ 4 files changed, 608 insertions(+) create mode 100644 docs/guides/pgbouncer/concepts/autoscaler.md create mode 100644 docs/guides/pgbouncer/concepts/opsrequest.md create mode 100755 docs/guides/pgbouncer/sync-users/_index.md create mode 100644 docs/guides/pgbouncer/sync-users/sync-users-pgbouncer.md diff --git a/docs/guides/pgbouncer/concepts/autoscaler.md b/docs/guides/pgbouncer/concepts/autoscaler.md new file mode 100644 index 000000000..103d91eb5 --- /dev/null +++ b/docs/guides/pgbouncer/concepts/autoscaler.md @@ -0,0 +1,73 @@ +--- +title: PgBouncerAutoscaler CRD +menu: + docs_{{ .version }}: + identifier: pb-autoscaler-concepts + name: PgBouncerAutoscaler + parent: pb-concepts-pgbouncer + weight: 35 +menu_name: docs_{{ .version }} +section_menu_id: guides +--- + +> New to KubeDB? Please start [here](/docs/README.md). + +# PgBouncerAutoscaler + +## What is PgBouncerAutoscaler + +`PgBouncerAutoscaler` is a Kubernetes `Custom Resource Definitions` (CRD). It provides a declarative configuration for autoscaling [PgBouncer](https://pgbouncer.net/mediawiki/index.php/Main_Page) compute resources of PgBouncer components in a Kubernetes native way. + +## PgBouncerAutoscaler CRD Specifications + +Like any official Kubernetes resource, a `PgBouncerAutoscaler` has `TypeMeta`, `ObjectMeta`, `Spec` and `Status` sections. + +Here, some sample `PgBouncerAutoscaler` CROs for autoscaling different components of pgbouncer is given below: + +**Sample `PgBouncerAutoscaler` for pgbouncer:** + +```yaml +apiVersion: autoscaling.kubedb.com/v1alpha1 +kind: PgBouncerAutoscaler +metadata: + name: pgbouncer-auto-scale + namespace: demo +spec: + databaseRef: + name: pgbouncer-server + compute: + pgbouncer: + trigger: "On" + podLifeTimeThreshold: 24h + minAllowed: + cpu: 250m + memory: 350Mi + maxAllowed: + cpu: 1 + memory: 1Gi + controlledResources: ["cpu", "memory"] + containerControlledValues: "RequestsAndLimits" + resourceDiffPercentage: 10 +``` + +Here, we are going to describe the various sections of a `PgBouncerAutoscaler` crd. + +A `PgBouncerAutoscaler` object has the following fields in the `spec` section. + +### spec.databaseRef + +`spec.databaseRef` is a required field that point to the [PgBouncer](/docs/guides/pgbouncer/concepts/pgbouncer.md) object for which the autoscaling will be performed. This field consists of the following sub-field: + +- **spec.databaseRef.name :** specifies the name of the [PgBouncer](/docs/guides/pgbouncer/concepts/pgbouncer.md) object. + +### spec.compute + +`spec.compute` specifies the autoscaling configuration for the compute resources i.e. cpu and memory of PgBouncer components. This field consists of the following sub-field: + +- `trigger` indicates if compute autoscaling is enabled for this component of the pgbouncer. If "On" then compute autoscaling is enabled. If "Off" then compute autoscaling is disabled. +- `minAllowed` specifies the minimal amount of resources that will be recommended, default is no minimum. +- `maxAllowed` specifies the maximum amount of resources that will be recommended, default is no maximum. +- `controlledResources` specifies which type of compute resources (cpu and memory) are allowed for autoscaling. Allowed values are "cpu" and "memory". +- `containerControlledValues` specifies which resource values should be controlled. Allowed values are "RequestsAndLimits" and "RequestsOnly". +- `resourceDiffPercentage` specifies the minimum resource difference between recommended value and the current value in percentage. If the difference percentage is greater than this value than autoscaling will be triggered. +- `podLifeTimeThreshold` specifies the minimum pod lifetime of at least one of the pods before triggering autoscaling. \ No newline at end of file diff --git a/docs/guides/pgbouncer/concepts/opsrequest.md b/docs/guides/pgbouncer/concepts/opsrequest.md new file mode 100644 index 000000000..45ba3aaa9 --- /dev/null +++ b/docs/guides/pgbouncer/concepts/opsrequest.md @@ -0,0 +1,330 @@ +--- +title: PgBouncerOpsRequests CRD +menu: + docs_{{ .version }}: + identifier: pb-opsrequest-concepts + name: PgBouncerOpsRequest + parent: pb-concepts-pgbouncer + weight: 25 +menu_name: docs_{{ .version }} +section_menu_id: guides +--- + +> New to KubeDB? Please start [here](/docs/README.md). + +# PgBouncerOpsRequest + +## What is PgBouncerOpsRequest + +`PgBouncerOpsRequest` is a Kubernetes `Custom Resource Definitions` (CRD). It provides a declarative configuration for [PgBouncer](https://pgbouncer.net/mediawiki/index.php/Main_Page) administrative operations like version updating, horizontal scaling, vertical scaling etc. in a Kubernetes native way. + +## PgBouncerOpsRequest CRD Specifications + +Like any official Kubernetes resource, a `PgBouncerOpsRequest` has `TypeMeta`, `ObjectMeta`, `Spec` and `Status` sections. + +Here, some sample `PgBouncerOpsRequest` CRs for different administrative operations is given below: + +**Sample `PgBouncerOpsRequest` for updating version:** + +```yaml +apiVersion: ops.kubedb.com/v1alpha1 +kind: PgBouncerOpsRequest +metadata: + name: pgbouncer-version-update + namespace: demo +spec: + type: UpdateVersion + databaseRef: + name: pgbouncer-server + updateVersion: + targetVersion: 1.18.0 +``` + +**Sample `PgBouncerOpsRequest` Objects for Horizontal Scaling:** + +```yaml +apiVersion: ops.kubedb.com/v1alpha1 +kind: PgBouncerOpsRequest +metadata: + name: pgbouncer-horizontal-scale + namespace: demo +spec: + type: HorizontalScaling + databaseRef: + name: pgbouncer-server + horizontalScaling: + replicas: 2 +``` + +**Sample `PgBouncerOpsRequest` Objects for Vertical Scaling:** + +```yaml +apiVersion: ops.kubedb.com/v1alpha1 +kind: PgBouncerOpsRequest +metadata: + name: pgbouncer-vertical-scale + namespace: demo +spec: + type: VerticalScaling + databaseRef: + name: pgbouncer-server + verticalScaling: + pgbouncer: + resources: + requests: + memory: "1200Mi" + cpu: "0.7" + limits: + memory: "1200Mi" + cpu: "0.7" +``` + +**Sample `PgBouncerOpsRequest` Objects for Reconfiguring:** + +```yaml +apiVersion: ops.kubedb.com/v1alpha1 +kind: PgBouncerOpsRequest +metadata: + name: pgbouncer-reconfigure + namespace: demo +spec: + type: Reconfigure + databaseRef: + name: pgbouncer-server + configuration: + pgbouncer: + applyConfig: + pgbouncer.conf: |- + auth_type = scram-sh-256 +``` + +```yaml +apiVersion: ops.kubedb.com/v1alpha1 +kind: PgBouncerOpsRequest +metadata: + name: pgbouncer-reconfigure + namespace: demo +spec: + type: Reconfigure + databaseRef: + name: pgbouncer-server + configuration: + removeCustomConfig: true +``` + +```yaml +apiVersion: ops.kubedb.com/v1alpha1 +kind: PgBouncerOpsRequest +metadata: + name: pgbouncer-reconfigure + namespace: demo +spec: + type: Reconfigure + databaseRef: + name: pgbouncer-server + configuration: + configSecret: + name: new-custom-config +``` + + +**Sample `PgBouncerOpsRequest` Objects for Reconfiguring TLS:** + +```yaml +apiVersion: ops.kubedb.com/v1alpha1 +kind: PgBouncerOpsRequest +metadata: + name: tls + namespace: demo +spec: + type: ReconfigureTLS + databaseRef: + name: pgbouncer-server + tls: + sslMode: verify-ca + clientAuthMode: cert + issuerRef: + name: pgbouncer-ca-issuer + kind: Issuer + apiGroup: "cert-manager.io" + certificates: + - alias: client + subject: + organizations: + - kubedb + organizationalUnits: + - client +``` + +```yaml +apiVersion: ops.kubedb.com/v1alpha1 +kind: PgBouncerOpsRequest +metadata: + name: tls + namespace: demo +spec: + type: ReconfigureTLS + databaseRef: + name: pgbouncer-server + tls: + rotateCertificates: true +``` + +```yaml +apiVersion: ops.kubedb.com/v1alpha1 +kind: PgBouncerOpsRequest +metadata: + name: tls + namespace: demo +spec: + type: ReconfigureTLS + databaseRef: + name: pgbouncer-server + tls: + remove: true +``` + +Here, we are going to describe the various sections of a `PgBouncerOpsRequest` crd. + +A `PgBouncerOpsRequest` object has the following fields in the `spec` section. + +### spec.databaseRef + +`spec.databaseRef` is a required field that point to the [PgBouncer](/docs/guides/pgbouncer/concepts/pgbouncer.md) object for which the administrative operations will be performed. This field consists of the following sub-field: + +- **spec.databaseRef.name :** specifies the name of the [PgBouncer](/docs/guides/pgbouncer/concepts/pgbouncer.md) object. + +### spec.type + +`spec.type` specifies the kind of operation that will be applied to the database. Currently, the following types of operations are allowed in `PgBouncerOpsRequest`. + +- `UpdateVersion` +- `HorizontalScaling` +- `VerticalScaling` +- `Reconfigure` +- `ReconfigureTLS` +- `Restart` + +> You can perform only one type of operation on a single `PgBouncerOpsRequest` CR. For example, if you want to update your database and scale up its replica then you have to create two separate `PgBouncerOpsRequest`. At first, you have to create a `PgBouncerOpsRequest` for updating. Once it is completed, then you can create another `PgBouncerOpsRequest` for scaling. + +> Note: There is an exception to the above statement. It is possible to specify both `spec.configuration` & `spec.verticalScaling` in a OpsRequest of type `VerticalScaling`. + +### spec.updateVersion + +If you want to update your PgBouncer version, you have to specify the `spec.updateVersion` section that specifies the desired version information. This field consists of the following sub-field: + +- `spec.updateVersion.targetVersion` refers to a [PgBouncerVersion](/docs/guides/pgbouncer/concepts/catalog.md) CR that contains the PgBouncer version information where you want to update. + + +### spec.horizontalScaling + +If you want to scale-up or scale-down your PgBouncer cluster or different components of it, you have to specify `spec.horizontalScaling` section. This field consists of the following sub-field: + +- `spec.horizontalScaling.replicas` indicates the desired number of pods for PgBouncer cluster after scaling. For example, if your cluster currently has 4 pods, and you want to add additional 2 pods then you have to specify 6 in `spec.horizontalScaling.replicas` field. Similarly, if you want to remove one pod from the cluster, you have to specify 3 in `spec.horizontalScaling.replicas` field. + +### spec.verticalScaling + +`spec.verticalScaling` is a required field specifying the information of resources like `cpu`, `memory` etc. that will be scaled. This field consists of the following sub-fields: + +- `spec.verticalScaling.pgbouncer` indicates the desired resources for PetSet of PgBouncer after scaling. +- `spec.verticalScaling.exporter` indicates the desired resources for PetSet of PgBouncer Exporter after scaling. + +It has the below structure: + +```yaml +requests: + memory: "200Mi" + cpu: "0.1" +limits: + memory: "300Mi" + cpu: "0.2" +``` + +Here, when you specify the resource request, the scheduler uses this information to decide which node to place the container of the Pod on and when you specify a resource limit for the container, the `kubelet` enforces those limits so that the running container is not allowed to use more of that resource than the limit you set. You can found more details from [here](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/). + + +### spec.configuration + +If you want to reconfigure your Running PgBouncer cluster or different components of it with new custom configuration, you have to specify `spec.configuration` section. This field consists of the following sub-field: + +- `configSecret` points to a secret in the same namespace of a PgBouncer resource, which contains the new custom configurations. If there are any configSecret set before in the database, this secret will replace it. +- `applyConfig` contains the new custom config as a string which will be merged with the previous configuration. + +- `applyConfig` is a map where key supports 1 values, namely `pgbouncer.ini`. + +```yaml + applyConfig: + pgbouncer.conf: |- + max_pool = 30 +``` + +- `removeCustomConfig` is a boolean field. Specify this field to true if you want to remove all the custom configuration from the deployed pgbouncer server. + +### spec.tls + +If you want to reconfigure the TLS configuration of your pgbouncer cluster i.e. add TLS, remove TLS, update issuer/cluster issuer or Certificates and rotate the certificates, you have to specify `spec.tls` section. This field consists of the following sub-field: + +- `spec.tls.issuerRef` specifies the issuer name, kind and api group. +- `spec.tls.certificates` specifies the certificates. You can learn more about this field from [here](/docs/guides/pgbouncer/concepts/pgbouncer.md#spectls). +- `spec.tls.rotateCertificates` specifies that we want to rotate the certificate of this database. +- `spec.tls.remove` specifies that we want to remove tls from this database. +- `spec.tls.sslMode` specifies what will be the ssl mode of the cluster allowed values are: disable,allow,prefer,require,verify-ca,verify-full +- `spec.tls.clientAuthMode` specifies what will be the client authentication mode of the cluster allowed values are: md5,scram,cert + +### spec.timeout +As we internally retry the ops request steps multiple times, This `timeout` field helps the users to specify the timeout for those steps of the ops request (in second). +If a step doesn't finish within the specified timeout, the ops request will result in failure. + +### spec.apply +This field controls the execution of opsRequest depending on the database state. It has two supported values: `Always` & `IfReady`. +Use IfReady, if you want to process the opsRequest only when the database is Ready. And use Always, if you want to process the execution of opsReq irrespective of the Database state. + + +### PgBouncerOpsRequest `Status` + +`.status` describes the current state and progress of a `PgBouncerOpsRequest` operation. It has the following fields: + +### status.phase + +`status.phase` indicates the overall phase of the operation for this `PgBouncerOpsRequest`. It can have the following three values: + +| Phase | Meaning | +|-------------|------------------------------------------------------------------------------------| +| Successful | KubeDB has successfully performed the operation requested in the PgBouncerOpsRequest | +| Progressing | KubeDB has started the execution of the applied PgBouncerOpsRequest | +| Failed | KubeDB has failed the operation requested in the PgBouncerOpsRequest | +| Denied | KubeDB has denied the operation requested in the PgBouncerOpsRequest | +| Skipped | KubeDB has skipped the operation requested in the PgBouncerOpsRequest | + +Important: Ops-manager Operator can skip an opsRequest, only if its execution has not been started yet & there is a newer opsRequest applied in the cluster. `spec.type` has to be same as the skipped one, in this case. + +### status.observedGeneration + +`status.observedGeneration` shows the most recent generation observed by the `PgBouncerOpsRequest` controller. + +### status.conditions + +`status.conditions` is an array that specifies the conditions of different steps of `PgBouncerOpsRequest` processing. Each condition entry has the following fields: + +- `types` specifies the type of the condition. PgBouncerOpsRequest has the following types of conditions: + +| Type | Meaning | +|--------------------------------|---------------------------------------------------------------------------| +| `Progressing` | Specifies that the operation is now in the progressing state | +| `Successful` | Specifies such a state that the operation on the database was successful. | +| `DatabasePauseSucceeded` | Specifies such a state that the database is paused by the operator | +| `ResumeDatabase` | Specifies such a state that the database is resumed by the operator | +| `Failed` | Specifies such a state that the operation on the database failed. | +| `UpdatePetSetResources` | Specifies such a state that the PetSet resources has been updated | +| `UpdatePetSet` | Specifies such a state that the PetSet has been updated | +| `IssueCertificatesSucceeded` | Specifies such a state that the tls certificate issuing is successful | +| `UpdateDatabase` | Specifies such a state that the CR of PgBouncer is updated | + +- The `status` field is a string, with possible values `True`, `False`, and `Unknown`. + - `status` will be `True` if the current transition succeeded. + - `status` will be `False` if the current transition failed. + - `status` will be `Unknown` if the current transition was denied. +- The `message` field is a human-readable message indicating details about the condition. +- The `reason` field is a unique, one-word, CamelCase reason for the condition's last transition. +- The `lastTransitionTime` field provides a timestamp for when the operation last transitioned from one state to another. +- The `observedGeneration` shows the most recent condition transition generation observed by the controller. diff --git a/docs/guides/pgbouncer/sync-users/_index.md b/docs/guides/pgbouncer/sync-users/_index.md new file mode 100755 index 000000000..43e4e2a7a --- /dev/null +++ b/docs/guides/pgbouncer/sync-users/_index.md @@ -0,0 +1,10 @@ +--- +title: Runtime users sync to PgBouncer +menu: + docs_{{ .version }}: + identifier: pb-sync-users + name: Sync Users + parent: pb-pgbouncer-guides + weight: 30 +menu_name: docs_{{ .version }} +--- diff --git a/docs/guides/pgbouncer/sync-users/sync-users-pgbouncer.md b/docs/guides/pgbouncer/sync-users/sync-users-pgbouncer.md new file mode 100644 index 000000000..641fadfc0 --- /dev/null +++ b/docs/guides/pgbouncer/sync-users/sync-users-pgbouncer.md @@ -0,0 +1,195 @@ +--- +title: Runtime users sync to PgBouncer +menu: + docs_{{ .version }}: + identifier: pb-sync-users-pgbouncer + name: Sync users pgbouncer + parent: pb-sync-users + weight: 10 +menu_name: docs_{{ .version }} +section_menu_id: guides +--- + +> New to KubeDB? Please start [here](/docs/README.md). + +# Using Sync Users + +KubeDB supports providing a way to add/update users to PgBouncer in runtime simply by creating secret with defined keys and labels. This tutorial will show you how to use KubeDB to sync a user to PgBouncer on runtime. + +## Before You Begin + +- At first, you need to have a Kubernetes cluster, and the kubectl command-line tool must be configured to communicate with your cluster. If you do not already have a cluster, you can create one by using [kind](https://kind.sigs.k8s.io/docs/user/quick-start/). + +- Now, install KubeDB cli on your workstation and KubeDB operator in your cluster following the steps [here](/docs/setup/README.md). + +- To keep things isolated, this tutorial uses a separate namespace called `demo` throughout this tutorial. Run the following command to prepare your cluster for this tutorial: + + ```bash + $ kubectl create ns demo + namespace/demo created + ``` + +> Note: The yaml files used in this tutorial are stored in [docs/examples/pgbouncer](https://github.com/kubedb/docs/tree/{{< param "info.version" >}}/docs/examples/pgbouncer) folder in GitHub repository [kubedb/docs](https://github.com/kubedb/docs). + +## Overview + +KubeDB operator allows us to sync additional Postgres users to PgBouncer on runtime by setting `spec.syncUsers` to `true`, if this option is true KubeDB operator searches for secrets in the namespace of the Postgres mentioned with some certain labels. Then if the secret have username and password as key KubeDB operator will sync the username and password to PgBouncer. Again not only to add a user but also this feature can also be used for updating a user's password. + +At first, we need to create a secret that contains a `user` key and a `password` key which contains the `username` and `password` respectively. Also, we need to add two labels `` and `postgreses.kubedb.com`. The namespace must be ``. Below given a sample structure of the secret. + +Example: + +```yaml +apiVersion: v1 +kind: Secret +metadata: + labels: + app.kubernetes.io/instance: ha-postgres + app.kubernetes.io/name: postgreses.kubedb.com + name: pg-user + namespace: demo +stringData: + password: "12345" + username: "alice" +``` +- `app.kubernetes.io/instance` should be same as`appbinding name mentioned in .spec.postgresRef.name`. +- `app.kubernetes.io/name` should be `postgreses.kubedb.com`. +- `namespace` should be same as `namespace mentioned in .spec.postgresRef.namespace`. + +In every `20 seconds` KubeDB operator will sync all the users to PgBouncer. + +Secrets provided by users are not managed by KubeDB, and therefore, won't be modified or garbage collected by the KubeDB operator (version 0.13.0 and higher). + +### Prepare Postgres +For a PgBouncer surely we will need a Postgres server so, prepare a KubeDB Postgres cluster using this [tutorial](/docs/guides/postgres/clustering/streaming_replication.md), or you can use any externally managed postgres but in that case you need to create an [appbinding](/docs/guides/pgbouncer/concepts/appbinding.md) yourself. In this tutorial we will use 3 node Postgres cluster named `ha-postgres`. + +### Prepare PgBouncer + +Now, we are going to deploy a `PgBouncer` with version `1.23.1`. + +### Deploy PgBouncer + +Below is the YAML of the `PgBouncer` CR that we are going to create, + +```yaml +apiVersion: kubedb.com/v1 +kind: PgBouncer +metadata: + name: pgbouncer-sync + namespace: demo +spec: + version: "1.23.1" + replicas: 1 + database: + syncUsers: true + databaseName: "postgres" + databaseRef: + name: "ha-postgres" + namespace: demo + deletionPolicy: WipeOut +``` + +Let's create the `PgBouncer` CR we have shown above, + +```bash +$ kubectl create -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/pgbouncer/sync-users/pgbouncer-sync.yaml +pgbouncer.kubedb.com/pgbouncer-sync created +``` + +Now, wait until `pgbouncer-sync` has status `Ready`. i.e, + +```bash +$ kubectl get pb -n demo +NAME TYPE VERSION STATUS AGE +pgbouncer-sync kubedb.com/v1 4.5.0 Ready 41s +``` + +### Sync Users + +Now, create a secret with structure defined [here](/docs/guides/pgbouncer/concepts/pgbouncer.md#specsyncusers). Below is the YAML of the `secret` that we are going to create, + +```yaml +apiVersion: v1 +kind: Secret +metadata: + labels: + app.kubernetes.io/instance: ha-postgres + app.kubernetes.io/name: postgreses.kubedb.com + name: sync-secret + namespace: demo +stringData: + password: "12345" + username: "john" +``` + +Now, create the secret by applying the yaml above. + +```bash +$ kubectl create -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/pgbouncer/sync-users/secret.yaml +secret/sync-secret created +``` + +Now, after `20 seconds` you can exec into the pgbouncer pod and find if the new user is there, + +```bash +$ kubectl exec -it -n demo pgbouncer-sync-0 -- /bin/sh +/$ cat /var/run/pgbouncer/secret/userlist +"postgres" "md5AESOmAkfj+zX8zXLm92d6Vup6a5yASiiGScoHNDTIgBwH8=" +"john" "md5AEScbLKDSMb+KVrILhh7XEmyQ==" +"pgbouncer" "md5AESOmAkfj+zX8zXLm92d6Vup6a5yASiiGScoHNDTIgBwH8=" +/$ exit +exit +``` +We can see that the user is there in PgBouncer. So, now let's create this user and try to use this user through PgBouncer. +Now, you can connect to this pgbouncer through [psql](https://www.postgresql.org/docs/current/app-psql.html). Before that we need to port-forward to the primary service of pgbouncer. + +```bash +$ kubectl port-forward svc/pgbouncer-sync -n demo 9999:5432 +Forwarding from 127.0.0.1:9999 -> 5432 +Forwarding from [::1]:9999 -> 5432 +``` +We will use the root Postgres user to create the user, so let's get the password for the root user, so that we can use it. +```bash +$ kubectl get secrets -n demo ha-postgres-auth -o jsonpath='{.data.\password}' | base64 -d +qEeuU6cu5aH!O9CI⏎ +``` +We can use this password now, +```bash +$ export PGPASSWORD='qEeuU6cu5aH!O9CI' +$ psql --host=localhost --port=9999 --username=postgres postgres +psql (16.3 (Ubuntu 16.3-1.pgdg22.04+1), server 16.1) +Type "help" for help. + +postgres=# CREATE USER john WITH PASSWORD '12345'; +CREATE ROLE +postgres=# exit +``` +Now, let's use this john user. +```bash +$ export PGPASSWORD='12345' +$ psql --host=localhost --port=9999 --username=john postgres +psql (16.3 (Ubuntu 16.3-1.pgdg22.04+1), server 16.1) +Type "help" for help. + +postgres=> exit +``` +So, we can successfully verify that the user is registered in PgBouncer and also we can use it. + +## Cleaning up + +To clean up the Kubernetes resources created by this tutorial, run: + +```bash +kubectl delete -n demo pb/pgbouncer-sync +kubectl delete -n demo secret/sync-secret +kubectl delete pg -n demo ha-postgres +kubectl delete ns demo +``` + +## Next Steps + +- Monitor your PgBouncer database with KubeDB using [out-of-the-box Prometheus operator](/docs/guides/pgbouncer/monitoring/using-prometheus-operator.md). +- Monitor your PgBouncer database with KubeDB using [out-of-the-box builtin-Prometheus](/docs/guides/pgbouncer/monitoring/using-builtin-prometheus.md). +- Detail concepts of [PgBouncer object](/docs/guides/pgbouncer/concepts/pgbouncer.md). +- Detail concepts of [PgBouncerVersion object](/docs/guides/pgbouncer/concepts/catalog.md). +- Want to hack on KubeDB? Check our [contribution guidelines](/docs/CONTRIBUTING.md). From 4852b58d60d9067082b54c9f1e09df974e8c1f3c Mon Sep 17 00:00:00 2001 From: Hiranmoy Das Chowdhury Date: Wed, 20 Nov 2024 15:15:18 +0600 Subject: [PATCH 02/29] update-version Signed-off-by: Hiranmoy Das Chowdhury --- .../guides/pgbouncer/update-version/_index.md | 10 + .../pgbouncer/update-version/overview.md | 54 ++++ .../update-version/update_version.md | 257 ++++++++++++++++++ 3 files changed, 321 insertions(+) create mode 100644 docs/guides/pgbouncer/update-version/_index.md create mode 100644 docs/guides/pgbouncer/update-version/overview.md create mode 100644 docs/guides/pgbouncer/update-version/update_version.md diff --git a/docs/guides/pgbouncer/update-version/_index.md b/docs/guides/pgbouncer/update-version/_index.md new file mode 100644 index 000000000..c8f0ef18d --- /dev/null +++ b/docs/guides/pgbouncer/update-version/_index.md @@ -0,0 +1,10 @@ +--- +title: Updating PgBouncer +menu: + docs_{{ .version }}: + identifier: pb-updating + name: UpdateVersion + parent: pb-pgbouncer-guides + weight: 40 +menu_name: docs_{{ .version }} +--- \ No newline at end of file diff --git a/docs/guides/pgbouncer/update-version/overview.md b/docs/guides/pgbouncer/update-version/overview.md new file mode 100644 index 000000000..95df6167c --- /dev/null +++ b/docs/guides/pgbouncer/update-version/overview.md @@ -0,0 +1,54 @@ +--- +title: Updating PgBouncer Overview +menu: + docs_{{ .version }}: + identifier: pb-updating-overview + name: Overview + parent: pb-updating + weight: 10 +menu_name: docs_{{ .version }} +section_menu_id: guides +--- + +> New to KubeDB? Please start [here](/docs/README.md). + +# updating PgBouncer version Overview + +This guide will give you an overview on how KubeDB Ops-manager operator update the version of `PgBouncer`. + +## Before You Begin + +- You should be familiar with the following `KubeDB` concepts: + - [PgBouncer](/docs/guides/pgbouncer/concepts/pgbouncer.md) + - [PgBouncerOpsRequest](/docs/guides/pgbouncer/concepts/opsrequest.md) + +## How update version Process Works + +The following diagram shows how KubeDB Ops-manager operator used to update the version of `PgBouncer`. Open the image in a new tab to see the enlarged version. + +
+  updating Process of PgBouncer +
Fig: updating Process of PgBouncer
+
+ +The updating process consists of the following steps: + +1. At first, a user creates a `PgBouncer` Custom Resource (CR). + +2. `KubeDB` Provisioner operator watches the `PgBouncer` CR. + +3. When the operator finds a `PgBouncer` CR, it creates required number of `PetSets` and related necessary stuff like secrets, services, etc. + +4. Then, in order to update the version of the `PgBouncer` the user creates a `PgBouncerOpsRequest` CR with the desired version. + +5. `KubeDB` Ops-manager operator watches the `PgBouncerOpsRequest` CR. + +6. When it finds a `PgBouncerOpsRequest` CR, it halts the `PgBouncer` object which is referred from the `PgBouncerOpsRequest`. So, the `KubeDB` Provisioner operator doesn't perform any operations on the `PgBouncer` object during the updating process. + +7. By looking at the target version from `PgBouncerOpsRequest` CR, `KubeDB` Ops-manager operator updates the image of the `PetSet`. + +8. After successfully updating the `PetSet` and their `Pods` images, the `KubeDB` Ops-manager operator updates the image of the `PgBouncer` object to reflect the updated state of the database. + +9. After successfully updating of `PgBouncer` object, the `KubeDB` Ops-manager operator resumes the `PgBouncer` object so that the `KubeDB` Provisioner operator can resume its usual operations. + +In the next doc, we are going to show a step-by-step guide on updating of a PgBouncer using updateVersion operation. \ No newline at end of file diff --git a/docs/guides/pgbouncer/update-version/update_version.md b/docs/guides/pgbouncer/update-version/update_version.md new file mode 100644 index 000000000..127a2ec40 --- /dev/null +++ b/docs/guides/pgbouncer/update-version/update_version.md @@ -0,0 +1,257 @@ +--- +title: Updating PgBouncer +menu: + docs_{{ .version }}: + identifier: pb-updating-pgbouncer + name: updatingPgBouncer + parent: pb-updating + weight: 20 +menu_name: docs_{{ .version }} +section_menu_id: guides +--- + +> New to KubeDB? Please start [here](/docs/README.md). + +# update version of PgBouncer + +This guide will show you how to use `KubeDB` Ops-manager operator to update the version of `PgBouncer`. + +## Before You Begin + +- At first, you need to have a Kubernetes cluster, and the `kubectl` command-line tool must be configured to communicate with your cluster. If you do not already have a cluster, you can create one by using [kind](https://kind.sigs.k8s.io/docs/user/quick-start/). + +- Install `KubeDB` Provisioner and Ops-manager operator in your cluster following the steps [here](/docs/setup/README.md). + +- You should be familiar with the following `KubeDB` concepts: + - [PgBouncer](/docs/guides/pgbouncer/concepts/pgbouncer.md) + - [PgBouncerOpsRequest](/docs/guides/pgbouncer/concepts/opsrequest.md) + - [Updating Overview](/docs/guides/pgbouncer/update-version/overview.md) + +To keep everything isolated, we are going to use a separate namespace called `demo` throughout this tutorial. + +```bash +$ kubectl create ns demo +namespace/demo created +``` + +> **Note:** YAML files used in this tutorial are stored in [docs/examples/pgbouncer](/docs/examples/pgbouncer) directory of [kubedb/docs](https://github.com/kube/docs) repository. + +### Prepare Postgres +Prepare a KubeDB Postgres cluster using this [tutorial](/docs/guides/postgres/clustering/streaming_replication.md), or you can use any externally managed postgres but in that case you need to create an [appbinding](/docs/guides/pgbouncer/concepts/appbinding.md) yourself. In this tutorial we will use 3 node Postgres cluster named `ha-postgres`. + +### Prepare PgBouncer + +Now, we are going to deploy a `PgBouncer` with version `1.18.0`. + +### Deploy PgBouncer: + +In this section, we are going to deploy a PgBouncer. Then, in the next section we will update the version using `PgBouncerOpsRequest` CRD. Below is the YAML of the `PgBouncer` CR that we are going to create, + +```yaml +apiVersion: kubedb.com/v1 +kind: PgBouncer +metadata: + name: pb-update + namespace: demo +spec: + replicas: 1 + version: "1.18.0" + database: + syncUsers: true + databaseName: "postgres" + databaseRef: + name: "ha-postgres" + namespace: demo + connectionPool: + poolMode: session + port: 5432 + reservePoolSize: 5 + maxClientConnections: 87 + defaultPoolSize: 2 + minPoolSize: 1 + authType: md5 + deletionPolicy: WipeOut +``` + +Let's create the `PgBouncer` CR we have shown above, + +```bash +$ kubectl create -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/pgbouncer/update-version/pb-update.yaml +pgbouncer.kubedb.com/pb-update created +``` + +Now, wait until `pb-update` created has status `Ready`. i.e, + +```bash +$ kubectl get pb -n demo + NAME TYPE VERSION STATUS AGE + pb-update kubedb.com/v1 1.18.0 Ready 26s +``` + +We are now ready to apply the `PgBouncerOpsRequest` CR to update this PgBouncer. + +### update PgBouncer Version + +Here, we are going to update `PgBouncer` from `1.18.0` to `1.23.1`. + +#### Create PgBouncerOpsRequest: + +In order to update the PgBouncer, we have to create a `PgBouncerOpsRequest` CR with your desired version that is supported by `KubeDB`. Below is the YAML of the `PgBouncerOpsRequest` CR that we are going to create, + +```yaml +apiVersion: ops.kubedb.com/v1alpha1 +kind: PgBouncerOpsRequest +metadata: + name: pgbouncer-version-update + namespace: demo +spec: + type: UpdateVersion + databaseRef: + name: pb-update + updateVersion: + targetVersion: 1.23.1 +``` + +Here, + +- `spec.databaseRef.name` specifies that we are performing operation on `pb-update` PgBouncer. +- `spec.type` specifies that we are going to perform `UpdateVersion` on our PgBouncer. +- `spec.updateVersion.targetVersion` specifies the expected version of the PgBouncer `1.23.1`. + + +Let's create the `PgBouncerOpsRequest` CR we have shown above, + +```bash +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/pgbouncer/update-version/pbops-update.yaml +pgbounceropsrequest.ops.kubedb.com/pgbouncer-version-update created +``` + +#### Verify PgBouncer version updated successfully : + +If everything goes well, `KubeDB` Ops-manager operator will update the image of `PgBouncer` object and related `PetSets` and `Pods`. + +Let's wait for `PgBouncerOpsRequest` to be `Successful`. Run the following command to watch `PgBouncerOpsRequest` CR, + +```bash +$ watch kubectl get pgbounceropsrequest -n demo +Every 2.0s: kubectl get pgbounceropsrequest -n demo +NAME TYPE STATUS AGE +pgbouncer-version-update UpdateVersion Successful 93s +``` + +We can see from the above output that the `PgBouncerOpsRequest` has succeeded. If we describe the `PgBouncerOpsRequest` we will get an overview of the steps that were followed to update the PgBouncer. + +```bash +$ kubectl describe pgbounceropsrequest -n demo pgbouncer-version-update +Name: pgbouncer-version-update +Namespace: demo +Labels: +Annotations: +API Version: ops.kubedb.com/v1alpha1 +Kind: PgBouncerOpsRequest +Metadata: + Creation Timestamp: 2024-07-17T06:31:58Z + Generation: 1 + Resource Version: 51165 + UID: 1409aec6-3a25-4b2b-90fe-02e5d8b1e8c1 +Spec: + Apply: IfReady + Database Ref: + Name: pb-update + Type: UpdateVersion + Update Version: + Target Version: 4.5.0 +Status: + Conditions: + Last Transition Time: 2024-07-17T06:31:58Z + Message: PgBouncer ops-request has started to update version + Observed Generation: 1 + Reason: UpdateVersion + Status: True + Type: UpdateVersion + Last Transition Time: 2024-07-17T06:32:01Z + Message: Successfully paused database + Observed Generation: 1 + Reason: DatabasePauseSucceeded + Status: True + Type: DatabasePauseSucceeded + Last Transition Time: 2024-07-17T06:32:07Z + Message: successfully reconciled the PgBouncer with updated version + Observed Generation: 1 + Reason: UpdatePetSets + Status: True + Type: UpdatePetSets + Last Transition Time: 2024-07-17T06:32:52Z + Message: Successfully Restarted PgBouncer pods + Observed Generation: 1 + Reason: RestartPods + Status: True + Type: RestartPods + Last Transition Time: 2024-07-17T06:32:12Z + Message: get pod; ConditionStatus:True; PodName:pb-update-0 + Observed Generation: 1 + Status: True + Type: GetPod--pb-update-0 + Last Transition Time: 2024-07-17T06:32:12Z + Message: evict pod; ConditionStatus:True; PodName:pb-update-0 + Observed Generation: 1 + Status: True + Type: EvictPod--pb-update-0 + Last Transition Time: 2024-07-17T06:32:47Z + Message: check pod running; ConditionStatus:True; PodName:pb-update-0 + Observed Generation: 1 + Status: True + Type: CheckPodRunning--pb-update-0 + Last Transition Time: 2024-07-17T06:32:52Z + Message: Successfully updated PgBouncer + Observed Generation: 1 + Reason: UpdateDatabase + Status: True + Type: UpdateDatabase + Last Transition Time: 2024-07-17T06:32:52Z + Message: Successfully updated PgBouncer version + Observed Generation: 1 + Reason: Successful + Status: True + Type: Successful + Observed Generation: 1 + Phase: Successful +Events: + Type Reason Age From Message + ---- ------ ---- ---- ------- + Normal Starting 2m55s KubeDB Ops-manager Operator Start processing for PgBouncerOpsRequest: demo/pgbouncer-version-update + Normal Starting 2m55s KubeDB Ops-manager Operator Pausing PgBouncer databse: demo/pb-update + Normal Successful 2m55s KubeDB Ops-manager Operator Successfully paused PgBouncer database: demo/pb-update for PgBouncerOpsRequest: pgbouncer-version-update + Normal UpdatePetSets 2m46s KubeDB Ops-manager Operator successfully reconciled the PgBouncer with updated version + Normal get pod; ConditionStatus:True; PodName:pb-update-0 2m41s KubeDB Ops-manager Operator get pod; ConditionStatus:True; PodName:pb-update-0 + Normal evict pod; ConditionStatus:True; PodName:pb-update-0 2m41s KubeDB Ops-manager Operator evict pod; ConditionStatus:True; PodName:pb-update-0 + Normal check pod running; ConditionStatus:False; PodName:pb-update-0 2m36s KubeDB Ops-manager Operator check pod running; ConditionStatus:False; PodName:pb-update-0 + Normal check pod running; ConditionStatus:True; PodName:pb-update-0 2m6s KubeDB Ops-manager Operator check pod running; ConditionStatus:True; PodName:pb-update-0 + Normal RestartPods 2m1s KubeDB Ops-manager Operator Successfully Restarted PgBouncer pods + Normal Starting 2m1s KubeDB Ops-manager Operator Resuming PgBouncer database: demo/pb-update + Normal Successful 2m1s KubeDB Ops-manager Operator Successfully resumed PgBouncer database: demo/pb-update for PgBouncerOpsRequest: pgbouncer-version-update +``` + +Now, we are going to verify whether the `PgBouncer` and the related `PetSets` their `Pods` have the new version image. Let's check, + +```bash +$ kubectl get pb -n demo pb-update -o=jsonpath='{.spec.version}{"\n"}' +1.23.1 + +$ kubectl get petset -n demo pb-update -o=jsonpath='{.spec.template.spec.containers[0].image}{"\n"}' +mongo:4.0.5 + +$ kubectl get pods -n demo mg-standalone-0 -o=jsonpath='{.spec.containers[0].image}{"\n"}' +ghcr.io/appscode-images/pgbouncer2:4.5.0@sha256:2697fcad9e11bdc704f6ae0fba85c4451c6b0243140aaaa33e719c3af548bda1 +``` + +You can see from above, our `PgBouncer` has been updated with the new version. So, the update process is successfully completed. + +## Cleaning Up + +To clean up the Kubernetes resources created by this tutorial, run: + +```bash +kubectl delete pb -n demo pb-update +kubectl delete pgbounceropsrequest -n demo pgbouncer-version-update +``` \ No newline at end of file From 07881852ab719e657db96cea2dfc04428c109253 Mon Sep 17 00:00:00 2001 From: Hiranmoy Das Chowdhury Date: Wed, 20 Nov 2024 18:15:23 +0600 Subject: [PATCH 03/29] scaling done Signed-off-by: Hiranmoy Das Chowdhury --- docs/guides/pgbouncer/scaling/_index.md | 10 + .../scaling/horizontal-scaling/_index.md | 10 + .../horizontal-scaling/horizontal-ops.md | 446 ++++++++++++++++++ .../scaling/horizontal-scaling/overview.md | 54 +++ .../scaling/vertical-scaling/_index.md | 10 + .../scaling/vertical-scaling/overview.md | 54 +++ .../scaling/vertical-scaling/vertical-ops.md | 293 ++++++++++++ 7 files changed, 877 insertions(+) create mode 100644 docs/guides/pgbouncer/scaling/_index.md create mode 100644 docs/guides/pgbouncer/scaling/horizontal-scaling/_index.md create mode 100644 docs/guides/pgbouncer/scaling/horizontal-scaling/horizontal-ops.md create mode 100644 docs/guides/pgbouncer/scaling/horizontal-scaling/overview.md create mode 100644 docs/guides/pgbouncer/scaling/vertical-scaling/_index.md create mode 100644 docs/guides/pgbouncer/scaling/vertical-scaling/overview.md create mode 100644 docs/guides/pgbouncer/scaling/vertical-scaling/vertical-ops.md diff --git a/docs/guides/pgbouncer/scaling/_index.md b/docs/guides/pgbouncer/scaling/_index.md new file mode 100644 index 000000000..dd2566b60 --- /dev/null +++ b/docs/guides/pgbouncer/scaling/_index.md @@ -0,0 +1,10 @@ +--- +title: Scaling PgBouncer +menu: + docs_{{ .version }}: + identifier: pb-scaling + name: Scaling + parent: pb-pgbouncer-guides + weight: 43 +menu_name: docs_{{ .version }} +--- \ No newline at end of file diff --git a/docs/guides/pgbouncer/scaling/horizontal-scaling/_index.md b/docs/guides/pgbouncer/scaling/horizontal-scaling/_index.md new file mode 100644 index 000000000..96af9d677 --- /dev/null +++ b/docs/guides/pgbouncer/scaling/horizontal-scaling/_index.md @@ -0,0 +1,10 @@ +--- +title: Horizontal Scaling +menu: + docs_{{ .version }}: + identifier: pb-horizontal-scaling + name: Horizontal Scaling + parent: pb-scaling + weight: 10 +menu_name: docs_{{ .version }} +--- \ No newline at end of file diff --git a/docs/guides/pgbouncer/scaling/horizontal-scaling/horizontal-ops.md b/docs/guides/pgbouncer/scaling/horizontal-scaling/horizontal-ops.md new file mode 100644 index 000000000..67f590614 --- /dev/null +++ b/docs/guides/pgbouncer/scaling/horizontal-scaling/horizontal-ops.md @@ -0,0 +1,446 @@ +--- +title: Horizontal Scaling PgBouncer +menu: + docs_{{ .version }}: + identifier: pb-horizontal-scaling-ops + name: HorizontalScaling OpsRequest + parent: pb-horizontal-scaling + weight: 20 +menu_name: docs_{{ .version }} +section_menu_id: guides +--- + +> New to KubeDB? Please start [here](/docs/README.md). + +# Horizontal Scale PgBouncer + +This guide will show you how to use `KubeDB` Ops-manager operator to scale the replicaset of a PgBouncer. + +## Before You Begin + +- At first, you need to have a Kubernetes cluster, and the `kubectl` command-line tool must be configured to communicate with your cluster. If you do not already have a cluster, you can create one by using [kind](https://kind.sigs.k8s.io/docs/user/quick-start/). + +- Install `KubeDB` Provisioner and Ops-manager operator in your cluster following the steps [here](/docs/setup/README.md). + +- You should be familiar with the following `KubeDB` concepts: + - [PgBouncer](/docs/guides/pgbouncer/concepts/pgbouncer.md) + - [PgBouncerOpsRequest](/docs/guides/pgbouncer/concepts/opsrequest.md) + - [Horizontal Scaling Overview](/docs/guides/pgbouncer/scaling/horizontal-scaling/overview.md) + +To keep everything isolated, we are going to use a separate namespace called `demo` throughout this tutorial. + +```bash +$ kubectl create ns demo +namespace/demo created +``` + +> **Note:** YAML files used in this tutorial are stored in [docs/examples/pgbouncer](/docs/examples/pgbouncer) directory of [kubedb/docs](https://github.com/kubedb/docs) repository. + +## Apply Horizontal Scaling on pgbouncer + +Here, we are going to deploy a `PgBouncer` using a supported version by `KubeDB` operator. Then we are going to apply horizontal scaling on it. + +### Prepare Postgres +Prepare a KubeDB Postgres cluster using this [tutorial](/docs/guides/postgres/clustering/streaming_replication.md), or you can use any externally managed postgres but in that case you need to create an [appbinding](/docs/guides/pgbouncer/concepts/appbinding.md) yourself. In this tutorial we will use 3 node Postgres cluster named `ha-postgres`. + +### Prepare PgBouncer + +Now, we are going to deploy a `PgBouncer` with version `1.23.1`. + +### Deploy PgBouncer + +In this section, we are going to deploy a PgBouncer. Then, in the next section we will scale the pgbouncer using `PgBouncerOpsRequest` CRD. Below is the YAML of the `PgBouncer` CR that we are going to create, + +```yaml +apiVersion: kubedb.com/v1 +kind: PgBouncer +metadata: + name: pb-horizontal + namespace: demo +spec: + replicas: 1 + version: "1.18.0" + database: + syncUsers: true + databaseName: "postgres" + databaseRef: + name: "ha-postgres" + namespace: demo + connectionPool: + poolMode: session + port: 5432 + reservePoolSize: 5 + maxClientConnections: 87 + defaultPoolSize: 2 + minPoolSize: 1 + authType: md5 + deletionPolicy: WipeOut +``` +Let's create the `PgBouncer` CR we have shown above, + +```bash +$ kubectl create -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/pgbouncer/scaling/pb-horizontal.yaml +pgbouncer.kubedb.com/pb-horizontal created +``` + +Now, wait until `pb-horizontal ` has status `Ready`. i.e, + +```bash +$ kubectl get pb -n demo +NAME TYPE VERSION STATUS AGE +pb-horizontal kubedb.com/v1alpha2 4.5.0 Ready 2m +``` + +Let's check the number of replicas this pgbouncer has from the PgBouncer object, number of pods the petset have, + +```bash +$ kubectl get pgbouncer -n demo pb-horizontal -o json | jq '.spec.replicas' +1 + +$ kubectl get petset -n demo pb-horizontal -o json | jq '.spec.replicas' +1 +``` + +We can see from both command that the pgbouncer has 3 replicas. + +We are now ready to apply the `PgBouncerOpsRequest` CR to scale this pgbouncer. + +## Scale Up Replicas + +Here, we are going to scale up the replicas of the pgbouncer to meet the desired number of replicas after scaling. + +#### Create PgBouncerOpsRequest + +In order to scale up the replicas of the pgbouncer, we have to create a `PgBouncerOpsRequest` CR with our desired replicas. Below is the YAML of the `PgBouncerOpsRequest` CR that we are going to create, + +```yaml +apiVersion: ops.kubedb.com/v1alpha1 +kind: PgBouncerOpsRequest +metadata: + name: pgbouncer-horizontal-scale-up + namespace: demo +spec: + type: HorizontalScaling + databaseRef: + name: pb-horizontal + horizontalScaling: + replicas: 3 +``` + +Here, + +- `spec.databaseRef.name` specifies that we are performing horizontal scaling operation on `pb-horizontal` pgbouncer. +- `spec.type` specifies that we are performing `HorizontalScaling` on our pgbouncer. +- `spec.horizontalScaling.replicas` specifies the desired replicas after scaling. + +Let's create the `PgBouncerOpsRequest` CR we have shown above, + +```bash +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/pgbouncer/scaling/horizontal-scaling/pbops-hscale-up-ops.yaml +pgbounceropsrequest.ops.kubedb.com/pgbouncer-horizontal-scale-up created +``` + +#### Verify replicas scaled up successfully + +If everything goes well, `KubeDB` Ops-manager operator will update the replicas of `PgBouncer` object and related `PetSet`. + +Let's wait for `PgBouncerOpsRequest` to be `Successful`. Run the following command to watch `PgBouncerOpsRequest` CR, + +```bash +$ watch kubectl get pgbounceropsrequest -n demo +Every 2.0s: kubectl get pgbounceropsrequest -n demo +NAME TYPE STATUS AGE +pgbouncer-horizontal-scale-up HorizontalScaling Successful 2m49s +``` + +We can see from the above output that the `PgBouncerOpsRequest` has succeeded. If we describe the `PgBouncerOpsRequest` we will get an overview of the steps that were followed to scale the pgbouncer. + +```bash +$ kubectl describe pgbounceropsrequest -n demo pgbouncer-horizontal-scale-up +Name: pgbouncer-horizontal-scale-up +Namespace: demo +Labels: +Annotations: +API Version: ops.kubedb.com/v1alpha1 +Kind: PgBouncerOpsRequest +Metadata: + Creation Timestamp: 2024-07-17T08:35:13Z + Generation: 1 + Resource Version: 62002 + UID: ce44f7a1-e78d-4248-a691-62fe1efd11f3 +Spec: + Apply: IfReady + Database Ref: + Name: pb-horizontal + Horizontal Scaling: + Node: 3 + Type: HorizontalScaling +Status: + Conditions: + Last Transition Time: 2024-07-17T08:35:13Z + Message: PgBouncer ops-request has started to horizontally scaling the nodes + Observed Generation: 1 + Reason: HorizontalScaling + Status: True + Type: HorizontalScaling + Last Transition Time: 2024-07-17T08:35:16Z + Message: Successfully paused database + Observed Generation: 1 + Reason: DatabasePauseSucceeded + Status: True + Type: DatabasePauseSucceeded + Last Transition Time: 2024-07-17T08:35:41Z + Message: Successfully Scaled Up Node + Observed Generation: 1 + Reason: HorizontalScaleUp + Status: True + Type: HorizontalScaleUp + Last Transition Time: 2024-07-17T08:35:21Z + Message: patch petset; ConditionStatus:True; PodName:pb-horizontal-1 + Observed Generation: 1 + Status: True + Type: PatchPetset--pb-horizontal-1 + Last Transition Time: 2024-07-17T08:35:26Z + Message: is pod ready; ConditionStatus:True; PodName:pb-horizontal-1 + Observed Generation: 1 + Status: True + Type: IsPodReady--pb-horizontal-1 + Last Transition Time: 2024-07-17T08:35:26Z + Message: client failure; ConditionStatus:True; PodName:pb-horizontal-1 + Observed Generation: 1 + Status: True + Type: ClientFailure--pb-horizontal-1 + Last Transition Time: 2024-07-17T08:35:26Z + Message: is node healthy; ConditionStatus:True; PodName:pb-horizontal-1 + Observed Generation: 1 + Status: True + Type: IsNodeHealthy--pb-horizontal-1 + Last Transition Time: 2024-07-17T08:35:31Z + Message: patch petset; ConditionStatus:True; PodName:pb-horizontal-2 + Observed Generation: 1 + Status: True + Type: PatchPetset--pb-horizontal-2 + Last Transition Time: 2024-07-17T08:35:31Z + Message: pb-horizontal already has desired replicas + Observed Generation: 1 + Reason: HorizontalScale + Status: True + Type: HorizontalScale + Last Transition Time: 2024-07-17T08:35:36Z + Message: is pod ready; ConditionStatus:True; PodName:pb-horizontal-2 + Observed Generation: 1 + Status: True + Type: IsPodReady--pb-horizontal-2 + Last Transition Time: 2024-07-17T08:35:36Z + Message: client failure; ConditionStatus:True; PodName:pb-horizontal-2 + Observed Generation: 1 + Status: True + Type: ClientFailure--pb-horizontal-2 + Last Transition Time: 2024-07-17T08:35:36Z + Message: is node healthy; ConditionStatus:True; PodName:pb-horizontal-2 + Observed Generation: 1 + Status: True + Type: IsNodeHealthy--pb-horizontal-2 + Last Transition Time: 2024-07-17T08:35:41Z + Message: Successfully updated PgBouncer + Observed Generation: 1 + Reason: UpdateDatabase + Status: True + Type: UpdateDatabase + Last Transition Time: 2024-07-17T08:35:41Z + Message: Successfully completed horizontally scale pgbouncer cluster + Observed Generation: 1 + Reason: Successful + Status: True + Type: Successful + Observed Generation: 1 + Phase: Successful +Events: + Type Reason Age From Message + ---- ------ ---- ---- ------- + Normal Starting 4m5s KubeDB Ops-manager Operator Start processing for PgBouncerOpsRequest: demo/pgbouncer-horizontal-scale-up + Normal Starting 4m5s KubeDB Ops-manager Operator Pausing PgBouncer databse: demo/pb-horizontal + Normal Successful 4m5s KubeDB Ops-manager Operator Successfully paused PgBouncer database: demo/pb-horizontal for PgBouncerOpsRequest: pgbouncer-horizontal-scale-up + Normal patch petset; ConditionStatus:True; PodName:pb-horizontal-1 3m57s KubeDB Ops-manager Operator patch petset; ConditionStatus:True; PodName:pb-horizontal-1 + Normal is pod ready; ConditionStatus:True; PodName:pb-horizontal-1 3m52s KubeDB Ops-manager Operator is pod ready; ConditionStatus:True; PodName:pb-horizontal-1 + Normal is node healthy; ConditionStatus:True; PodName:pb-horizontal-1 3m52s KubeDB Ops-manager Operator is node healthy; ConditionStatus:True; PodName:pb-horizontal-1 + Normal patch petset; ConditionStatus:True; PodName:pb-horizontal-2 3m47s KubeDB Ops-manager Operator patch petset; ConditionStatus:True; PodName:pb-horizontal-2 + Normal is pod ready; ConditionStatus:True; PodName:pb-horizontal-2 3m42s KubeDB Ops-manager Operator is pod ready; ConditionStatus:True; PodName:pb-horizontal-2 + Normal is node healthy; ConditionStatus:True; PodName:pb-horizontal-2 3m42s KubeDB Ops-manager Operator is node healthy; ConditionStatus:True; PodName:pb-horizontal-2 + Normal HorizontalScaleUp 3m37s KubeDB Ops-manager Operator Successfully Scaled Up Node + Normal UpdateDatabase 3m37s KubeDB Ops-manager Operator Successfully updated PgBouncer + Normal Starting 3m37s KubeDB Ops-manager Operator Resuming PgBouncer database: demo/pb-horizontal + Normal Successful 3m37s KubeDB Ops-manager Operator Successfully resumed PgBouncer database: demo/pb-horizontal for PgBouncerOpsRequest: pgbouncer-horizontal-scale-up +``` + +Now, we are going to verify the number of replicas this pgbouncer has from the PgBouncer object, number of pods the petset have, + +```bash +$ kubectl get pb -n demo pb-horizontal -o json | jq '.spec.replicas' +3 + +$ kubectl get petset -n demo pb-horizontal -o json | jq '.spec.replicas' +3 +``` +From all the above outputs we can see that the replicas of the pgbouncer is `3`. That means we have successfully scaled up the replicas of the PgBouncer. + + +### Scale Down Replicas + +Here, we are going to scale down the replicas of the pgbouncer to meet the desired number of replicas after scaling. + +#### Create PgBouncerOpsRequest + +In order to scale down the replicas of the pgbouncer, we have to create a `PgBouncerOpsRequest` CR with our desired replicas. Below is the YAML of the `PgBouncerOpsRequest` CR that we are going to create, + +```yaml +apiVersion: ops.kubedb.com/v1alpha1 +kind: PgBouncerOpsRequest +metadata: + name: pgbouncer-horizontal-scale-down + namespace: demo +spec: + type: HorizontalScaling + databaseRef: + name: pb-horizontal + horizontalScaling: + replicas: 2 +``` + +Here, + +- `spec.databaseRef.name` specifies that we are performing horizontal scaling down operation on `pb-horizontal` pgbouncer. +- `spec.type` specifies that we are performing `HorizontalScaling` on our pgbouncer. +- `spec.horizontalScaling.replicas` specifies the desired replicas after scaling. + +Let's create the `PgBouncerOpsRequest` CR we have shown above, + +```bash +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/pgbouncer/scaling/horizontal-scaling/pbops-hscale-down-ops.yaml +pgbounceropsrequest.ops.kubedb.com/pgbouncer-horizontal-scale-down created +``` + +#### Verify replicas scaled down successfully + +If everything goes well, `KubeDB` Ops-manager operator will update the replicas of `PgBouncer` object and related `PetSet`. + +Let's wait for `PgBouncerOpsRequest` to be `Successful`. Run the following command to watch `PgBouncerOpsRequest` CR, + +```bash +$ watch kubectl get pgbounceropsrequest -n demo +Every 2.0s: kubectl get pgbounceropsrequest -n demo +NAME TYPE STATUS AGE +pgbouncer-horizontal-scale-down HorizontalScaling Successful 75s +``` + +We can see from the above output that the `PgBouncerOpsRequest` has succeeded. If we describe the `PgBouncerOpsRequest` we will get an overview of the steps that were followed to scale the pgbouncer. + +```bash +$ kubectl describe pgbounceropsrequest -n demo pgbouncer-horizontal-scale-down +Name: pgbouncer-horizontal-scale-down +Namespace: demo +Labels: +Annotations: +API Version: ops.kubedb.com/v1alpha1 +Kind: PgBouncerOpsRequest +Metadata: + Creation Timestamp: 2024-07-17T08:52:28Z + Generation: 1 + Resource Version: 63600 + UID: 019f9d8f-c2b0-4154-b3d3-b715b8805fd7 +Spec: + Apply: IfReady + Database Ref: + Name: pb-horizontal + Horizontal Scaling: + Node: 2 + Type: HorizontalScaling +Status: + Conditions: + Last Transition Time: 2024-07-17T08:52:28Z + Message: PgBouncer ops-request has started to horizontally scaling the nodes + Observed Generation: 1 + Reason: HorizontalScaling + Status: True + Type: HorizontalScaling + Last Transition Time: 2024-07-17T08:52:31Z + Message: Successfully paused database + Observed Generation: 1 + Reason: DatabasePauseSucceeded + Status: True + Type: DatabasePauseSucceeded + Last Transition Time: 2024-07-17T08:53:16Z + Message: Successfully Scaled Down Node + Observed Generation: 1 + Reason: HorizontalScaleDown + Status: True + Type: HorizontalScaleDown + Last Transition Time: 2024-07-17T08:52:36Z + Message: patch petset; ConditionStatus:True; PodName:pb-horizontal-2 + Observed Generation: 1 + Status: True + Type: PatchPetset--pb-horizontal-2 + Last Transition Time: 2024-07-17T08:52:36Z + Message: pb-horizontal already has desired replicas + Observed Generation: 1 + Reason: HorizontalScale + Status: True + Type: HorizontalScale + Last Transition Time: 2024-07-17T08:52:41Z + Message: get pod; ConditionStatus:False + Observed Generation: 1 + Status: False + Type: GetPod + Last Transition Time: 2024-07-17T08:53:11Z + Message: get pod; ConditionStatus:True; PodName:pb-horizontal-2 + Observed Generation: 1 + Status: True + Type: GetPod--pb-horizontal-2 + Last Transition Time: 2024-07-17T08:53:16Z + Message: Successfully updated PgBouncer + Observed Generation: 1 + Reason: UpdateDatabase + Status: True + Type: UpdateDatabase + Last Transition Time: 2024-07-17T08:53:16Z + Message: Successfully completed horizontally scale pgbouncer cluster + Observed Generation: 1 + Reason: Successful + Status: True + Type: Successful + Observed Generation: 1 + Phase: Successful +Events: + Type Reason Age From Message + ---- ------ ---- ---- ------- + Normal Starting 96s KubeDB Ops-manager Operator Start processing for PgBouncerOpsRequest: demo/pgbouncer-horizontal-scale-down + Normal Starting 96s KubeDB Ops-manager Operator Pausing PgBouncer databse: demo/pb-horizontal + Normal Successful 96s KubeDB Ops-manager Operator Successfully paused PgBouncer database: demo/pb-horizontal for PgBouncerOpsRequest: pgbouncer-horizontal-scale-down + Normal patch petset; ConditionStatus:True; PodName:pb-horizontal-2 88s KubeDB Ops-manager Operator patch petset; ConditionStatus:True; PodName:pb-horizontal-2 + Normal get pod; ConditionStatus:False 83s KubeDB Ops-manager Operator get pod; ConditionStatus:False + Normal get pod; ConditionStatus:True; PodName:pb-horizontal-2 53s KubeDB Ops-manager Operator get pod; ConditionStatus:True; PodName:pb-horizontal-2 + Normal HorizontalScaleDown 48s KubeDB Ops-manager Operator Successfully Scaled Down Node + Normal UpdateDatabase 48s KubeDB Ops-manager Operator Successfully updated PgBouncer + Normal Starting 48s KubeDB Ops-manager Operator Resuming PgBouncer database: demo/pb-horizontal + Normal Successful 48s KubeDB Ops-manager Operator Successfully resumed PgBouncer database: demo/pb-horizontal for PgBouncerOpsRequest: pgbouncer-horizontal-scale-down +``` + +Now, we are going to verify the number of replicas this pgbouncer has from the PgBouncer object, number of pods the petset have, + +```bash +$ kubectl get pb -n demo pb-horizontal -o json | jq '.spec.replicas' +2 + +$ kubectl get petset -n demo pb-horizontal -o json | jq '.spec.replicas' +2 +``` +From all the above outputs we can see that the replicas of the pgbouncer is `2`. That means we have successfully scaled down the replicas of the PgBouncer. + +## Cleaning Up + +To clean up the Kubernetes resources created by this tutorial, run: + +```bash +kubectl delete pb -n demo pb-horizontal +kubectl delete pgbounceropsrequest -n demo pgbouncer-horizontal-scale-down +``` \ No newline at end of file diff --git a/docs/guides/pgbouncer/scaling/horizontal-scaling/overview.md b/docs/guides/pgbouncer/scaling/horizontal-scaling/overview.md new file mode 100644 index 000000000..06eeb25f8 --- /dev/null +++ b/docs/guides/pgbouncer/scaling/horizontal-scaling/overview.md @@ -0,0 +1,54 @@ +--- +title: Pgpool Horizontal Scaling Overview +menu: + docs_{{ .version }}: + identifier: pb-horizontal-scaling-overview + name: Overview + parent: pb-horizontal-scaling + weight: 10 +menu_name: docs_{{ .version }} +section_menu_id: guides +--- + +> New to KubeDB? Please start [here](/docs/README.md). + +# Pgpool Horizontal Scaling + +This guide will give an overview on how KubeDB Ops-manager operator scales up or down `Pgpool` replicas of PetSet. + +## Before You Begin + +- You should be familiar with the following `KubeDB` concepts: + - [Pgpool](/docs/guides/pgpool/concepts/pgpool.md) + - [PgpoolOpsRequest](/docs/guides/pgpool/concepts/opsrequest.md) + +## How Horizontal Scaling Process Works + +The following diagram shows how KubeDB Ops-manager operator scales up or down `Pgpool` database components. Open the image in a new tab to see the enlarged version. + +
+  Horizontal scaling process of Pgpool +
Fig: Horizontal scaling process of Pgpool
+
+ +The Horizontal scaling process consists of the following steps: + +1. At first, a user creates a `Pgpool` Custom Resource (CR). + +2. `KubeDB` Provisioner operator watches the `Pgpool` CR. + +3. When the operator finds a `Pgpool` CR, it creates `PetSet` and related necessary stuff like secrets, services, etc. + +4. Then, in order to scale the `PetSet` of the `Pgpool` database the user creates a `PgpoolOpsRequest` CR with desired information. + +5. `KubeDB` Ops-manager operator watches the `PgpoolOpsRequest` CR. + +6. When it finds a `PgpoolOpsRequest` CR, it pauses the `Pgpool` object which is referred from the `PgpoolOpsRequest`. So, the `KubeDB` Provisioner operator doesn't perform any operations on the `Pgpool` object during the horizontal scaling process. + +7. Then the `KubeDB` Ops-manager operator will scale the related PetSet Pods to reach the expected number of replicas defined in the `PgpoolOpsRequest` CR. + +8. After the successfully scaling the replicas of the related PetSet Pods, the `KubeDB` Ops-manager operator updates the number of replicas in the `Pgpool` object to reflect the updated state. + +9. After the successful scaling of the `Pgpool` replicas, the `KubeDB` Ops-manager operator resumes the `Pgpool` object so that the `KubeDB` Provisioner operator resumes its usual operations. + +In the next docs, we are going to show a step-by-step guide on horizontal scaling of Pgpool using `PgpoolOpsRequest` CRD. \ No newline at end of file diff --git a/docs/guides/pgbouncer/scaling/vertical-scaling/_index.md b/docs/guides/pgbouncer/scaling/vertical-scaling/_index.md new file mode 100644 index 000000000..da67c6d84 --- /dev/null +++ b/docs/guides/pgbouncer/scaling/vertical-scaling/_index.md @@ -0,0 +1,10 @@ +--- +title: Vertical Scaling +menu: + docs_{{ .version }}: + identifier: pb-vertical-scaling + name: Vertical Scaling + parent: pb-scaling + weight: 20 +menu_name: docs_{{ .version }} +--- \ No newline at end of file diff --git a/docs/guides/pgbouncer/scaling/vertical-scaling/overview.md b/docs/guides/pgbouncer/scaling/vertical-scaling/overview.md new file mode 100644 index 000000000..5ec7f24c8 --- /dev/null +++ b/docs/guides/pgbouncer/scaling/vertical-scaling/overview.md @@ -0,0 +1,54 @@ +--- +title: PgBouncer Vertical Scaling Overview +menu: + docs_{{ .version }}: + identifier: pb-vertical-scaling-overview + name: Overview + parent: pb-vertical-scaling + weight: 10 +menu_name: docs_{{ .version }} +section_menu_id: guides +--- + +> New to KubeDB? Please start [here](/docs/README.md). + +# PgBouncer Vertical Scaling + +This guide will give an overview on how KubeDB Ops-manager operator updates the resources(for example CPU and Memory etc.) of the `PgBouncer`. + +## Before You Begin + +- You should be familiar with the following `KubeDB` concepts: + - [PgBouncer](/docs/guides/pgbouncer/concepts/pgbouncer.md) + - [PgBouncerOpsRequest](/docs/guides/pgbouncer/concepts/opsrequest.md) + +## How Vertical Scaling Process Works + +The following diagram shows how KubeDB Ops-manager operator updates the resources of the `PgBouncer`. Open the image in a new tab to see the enlarged version. + +
+  Vertical scaling process of PgBouncer +
Fig: Vertical scaling process of PgBouncer
+
+ +The vertical scaling process consists of the following steps: + +1. At first, a user creates a `PgBouncer` Custom Resource (CR). + +2. `KubeDB` Provisioner operator watches the `PgBouncer` CR. + +3. When the operator finds a `PgBouncer` CR, it creates `PetSet` and related necessary stuff like secrets, services, etc. + +4. Then, in order to update the resources(for example `CPU`, `Memory` etc.) of the `PgBouncer`, the user creates a `PgBouncerOpsRequest` CR with desired information. + +5. `KubeDB` Ops-manager operator watches the `PgBouncerOpsRequest` CR. + +6. When it finds a `PgBouncerOpsRequest` CR, it pauses the `PgBouncer` object which is referred from the `PgBouncerOpsRequest`. So, the `KubeDB` Provisioner operator doesn't perform any operations on the `PgBouncer` object during the vertical scaling process. + +7. Then the `KubeDB` Ops-manager operator will update resources of the PetSet to reach desired state. + +8. After the successful update of the resources of the PetSet's replica, the `KubeDB` Ops-manager operator updates the `PgBouncer` object to reflect the updated state. + +9. After the successful update of the `PgBouncer` resources, the `KubeDB` Ops-manager operator resumes the `PgBouncer` object so that the `KubeDB` Provisioner operator resumes its usual operations. + +In the next docs, we are going to show a step-by-step guide on updating resources of PgBouncer `PgBouncerOpsRequest` CRD. \ No newline at end of file diff --git a/docs/guides/pgbouncer/scaling/vertical-scaling/vertical-ops.md b/docs/guides/pgbouncer/scaling/vertical-scaling/vertical-ops.md new file mode 100644 index 000000000..25c508356 --- /dev/null +++ b/docs/guides/pgbouncer/scaling/vertical-scaling/vertical-ops.md @@ -0,0 +1,293 @@ +--- +title: Vertical Scaling PgBouncer +menu: + docs_{{ .version }}: + identifier: pb-vertical-scaling-ops + name: VerticalScaling OpsRequest + parent: pb-vertical-scaling + weight: 20 +menu_name: docs_{{ .version }} +section_menu_id: guides +--- + +> New to KubeDB? Please start [here](/docs/README.md). + +# Vertical Scale PgBouncer + +This guide will show you how to use `KubeDB` Ops-manager operator to update the resources of a PgBouncer. + +## Before You Begin + +- At first, you need to have a Kubernetes cluster, and the `kubectl` command-line tool must be configured to communicate with your cluster. If you do not already have a cluster, you can create one by using [kind](https://kind.sigs.k8s.io/docs/user/quick-start/). + +- Install `KubeDB` Provisioner and Ops-manager operator in your cluster following the steps [here](/docs/setup/README.md). + +- You should be familiar with the following `KubeDB` concepts: + - [PgBouncer](/docs/guides/pgbouncer/concepts/pgbouncer.md) + - [PgBouncerOpsRequest](/docs/guides/pgbouncer/concepts/opsrequest.md) + - [Vertical Scaling Overview](/docs/guides/pgbouncer/scaling/vertical-scaling/overview.md) + +To keep everything isolated, we are going to use a separate namespace called `demo` throughout this tutorial. + +```bash +$ kubectl create ns demo +namespace/demo created +``` + +> **Note:** YAML files used in this tutorial are stored in [docs/examples/pgbouncer](/docs/examples/pgbouncer) directory of [kubedb/docs](https://github.com/kubedb/docs) repository. + +## Apply Vertical Scaling on PgBouncer + +Here, we are going to deploy a `PgBouncer` using a supported version by `KubeDB` operator. Then we are going to apply vertical scaling on it. + +### Prepare Postgres +Prepare a KubeDB Postgres cluster using this [tutorial](/docs/guides/postgres/clustering/streaming_replication.md), or you can use any externally managed postgres but in that case you need to create an [appbinding](/docs/guides/pgbouncer/concepts/appbinding.md) yourself. In this tutorial we will use 3 node Postgres cluster named `ha-postgres`. + +### Prepare PgBouncer + +Now, we are going to deploy a `PgBouncer` with version `1.18.0`. + +### Deploy PgBouncer + +In this section, we are going to deploy a PgBouncer. Then, in the next section we will update the resources using `PgBouncerOpsRequest` CRD. Below is the YAML of the `PgBouncer` CR that we are going to create, + +```yaml +apiVersion: kubedb.com/v1 +kind: PgBouncer +metadata: + name: pb-vertical + namespace: demo +spec: + replicas: 1 + version: "1.18.0" + database: + syncUsers: true + databaseName: "postgres" + databaseRef: + name: "ha-postgres" + namespace: demo + connectionPool: + poolMode: session + port: 5432 + reservePoolSize: 5 + maxClientConnections: 87 + defaultPoolSize: 2 + minPoolSize: 1 + authType: md5 + deletionPolicy: WipeOut +``` + +Let's create the `PgBouncer` CR we have shown above, + +```bash +$ kubectl create -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/pgbouncer/scaling/pb-vertical.yaml +pgbouncer.kubedb.com/pb-vertical created +``` + +Now, wait until `pb-vertical` has status `Ready`. i.e, + +```bash +$ kubectl get pb -n demo +NAME TYPE VERSION STATUS AGE +pb-vertical kubedb.com/v1 1.18.0 Ready 17s +``` + +Let's check the Pod containers resources, + +```bash +$ kubectl get pod -n demo pb-vertical-0 -o json | jq '.spec.containers[].resources' +{ + "limits": { + "cpu": "500m", + "memory": "1Gi" + }, + "requests": { + "cpu": "500m", + "memory": "1Gi" + } +} +``` + +You can see the Pod has default resources which is assigned by the KubeDB operator. + +We are now ready to apply the `PgBouncerOpsRequest` CR to update the resources of this pgbouncer. + +### Vertical Scaling + +Here, we are going to update the resources of the pgbouncer to meet the desired resources after scaling. + +#### Create PgBouncerOpsRequest + +In order to update the resources of the pgbouncer, we have to create a `PgBouncerOpsRequest` CR with our desired resources. Below is the YAML of the `PgBouncerOpsRequest` CR that we are going to create, + +```yaml +apiVersion: ops.kubedb.com/v1alpha1 +kind: PgBouncerOpsRequest +metadata: + name: pgbouncer-scale-vertical + namespace: demo +spec: + type: VerticalScaling + databaseRef: + name: pb-vertical + verticalScaling: + pgbouncer: + resources: + requests: + memory: "2Gi" + cpu: "1" + limits: + memory: "2Gi" + cpu: "1" + timeout: 5m + apply: IfReady +``` + +Here, + +- `spec.databaseRef.name` specifies that we are performing vertical scaling operation on `pb-vertical` pgbouncer. +- `spec.type` specifies that we are performing `VerticalScaling` on our database. +- `spec.VerticalScaling.pgbouncer` specifies the desired resources after scaling. +- Have a look [here](/docs/guides/pgbouncer/concepts/opsrequest.md) on the respective sections to understand the `timeout` & `apply` fields. + +Let's create the `PgBouncerOpsRequest` CR we have shown above, + +```bash +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/pgbouncer/scaling/vertical-scaling/pb-vertical-ops.yaml +pgbounceropsrequest.ops.kubedb.com/pgbouncer-scale-vertical created +``` + +#### Verify PgBouncer resources updated successfully + +If everything goes well, `KubeDB` Ops-manager operator will update the resources of `PgBouncer` object and related `PetSet` and `Pods`. + +Let's wait for `PgBouncerOpsRequest` to be `Successful`. Run the following command to watch `PgBouncerOpsRequest` CR, + +```bash +$ kubectl get pgbounceropsrequest -n demo +Every 2.0s: kubectl get pgbounceropsrequest -n demo +NAME TYPE STATUS AGE +pgbouncer-scale-vertical VerticalScaling Successful 3m42s +``` + +We can see from the above output that the `PgBouncerOpsRequest` has succeeded. If we describe the `PgBouncerOpsRequest` we will get an overview of the steps that were followed to scale the pgbouncer. + +```bash +$ kubectl describe pgbounceropsrequest -n demo pgbouncer-scale-vertical +Name: pgbouncer-scale-vertical +Namespace: demo +Labels: +Annotations: +API Version: ops.kubedb.com/v1alpha1 +Kind: PgBouncerOpsRequest +Metadata: + Creation Timestamp: 2024-07-17T09:44:22Z + Generation: 1 + Resource Version: 68270 + UID: 62a105f7-e7b9-444e-9303-79818fccfdef +Spec: + Apply: IfReady + Database Ref: + Name: pb-vertical + Timeout: 5m + Type: VerticalScaling + Vertical Scaling: + Node: + Resources: + Limits: + Cpu: 1 + Memory: 2Gi + Requests: + Cpu: 1 + Memory: 2Gi +Status: + Conditions: + Last Transition Time: 2024-07-17T09:44:22Z + Message: PgBouncer ops-request has started to vertically scaling the PgBouncer nodes + Observed Generation: 1 + Reason: VerticalScaling + Status: True + Type: VerticalScaling + Last Transition Time: 2024-07-17T09:44:25Z + Message: Successfully paused database + Observed Generation: 1 + Reason: DatabasePauseSucceeded + Status: True + Type: DatabasePauseSucceeded + Last Transition Time: 2024-07-17T09:44:25Z + Message: Successfully updated PetSets Resources + Observed Generation: 1 + Reason: UpdatePetSets + Status: True + Type: UpdatePetSets + Last Transition Time: 2024-07-17T09:45:10Z + Message: Successfully Restarted Pods With Resources + Observed Generation: 1 + Reason: RestartPods + Status: True + Type: RestartPods + Last Transition Time: 2024-07-17T09:44:30Z + Message: get pod; ConditionStatus:True; PodName:pb-vertical-0 + Observed Generation: 1 + Status: True + Type: GetPod--pb-vertical-0 + Last Transition Time: 2024-07-17T09:44:30Z + Message: evict pod; ConditionStatus:True; PodName:pb-vertical-0 + Observed Generation: 1 + Status: True + Type: EvictPod--pb-vertical-0 + Last Transition Time: 2024-07-17T09:45:05Z + Message: check pod running; ConditionStatus:True; PodName:pb-vertical-0 + Observed Generation: 1 + Status: True + Type: CheckPodRunning--pb-vertical-0 + Last Transition Time: 2024-07-17T09:45:10Z + Message: Successfully completed the vertical scaling for PgBouncer + Observed Generation: 1 + Reason: Successful + Status: True + Type: Successful + Observed Generation: 1 + Phase: Successful +Events: + Type Reason Age From Message + ---- ------ ---- ---- ------- + Normal Starting 4m16s KubeDB Ops-manager Operator Start processing for PgBouncerOpsRequest: demo/pgbouncer-scale-vertical + Normal Starting 4m16s KubeDB Ops-manager Operator Pausing PgBouncer databse: demo/pb-vertical + Normal Successful 4m16s KubeDB Ops-manager Operator Successfully paused PgBouncer database: demo/pb-vertical for PgBouncerOpsRequest: pgbouncer-scale-vertical + Normal UpdatePetSets 4m13s KubeDB Ops-manager Operator Successfully updated PetSets Resources + Warning get pod; ConditionStatus:True; PodName:pb-vertical-0 4m8s KubeDB Ops-manager Operator get pod; ConditionStatus:True; PodName:pb-vertical-0 + Warning evict pod; ConditionStatus:True; PodName:pb-vertical-0 4m8s KubeDB Ops-manager Operator evict pod; ConditionStatus:True; PodName:pb-vertical-0 + Warning check pod running; ConditionStatus:False; PodName:pb-vertical-0 4m3s KubeDB Ops-manager Operator check pod running; ConditionStatus:False; PodName:pb-vertical-0 + Warning check pod running; ConditionStatus:True; PodName:pb-vertical-0 3m33s KubeDB Ops-manager Operator check pod running; ConditionStatus:True; PodName:pb-vertical-0 + Normal RestartPods 3m28s KubeDB Ops-manager Operator Successfully Restarted Pods With Resources + Normal Starting 3m28s KubeDB Ops-manager Operator Resuming PgBouncer database: demo/pb-vertical + Normal Successful 3m28s KubeDB Ops-manager Operator Successfully resumed PgBouncer database: demo/pb-vertical for PgBouncerOpsRequest: pgbouncer-scale-vertical +``` + +Now, we are going to verify from the Pod yaml whether the resources of the pgbouncer has updated to meet up the desired state, Let's check, + +```bash +$ kubectl get pod -n demo pb-vertical-0 -o json | jq '.spec.containers[].resources' +{ + "limits": { + "cpu": "1", + "memory": "2Gi" + }, + "requests": { + "cpu": "1", + "memory": "2Gi" + } +} +``` + +The above output verifies that we have successfully scaled up the resources of the PgBouncer. + +## Cleaning Up + +To clean up the Kubernetes resources created by this tutorial, run: + +```bash +kubectl delete pb -n demo pb-vertical +kubectl delete pgbounceropsrequest -n demo pgbouncer-scale-vertical +``` \ No newline at end of file From 2bf213937d6a63eff83a706da24c29685ebe41ab Mon Sep 17 00:00:00 2001 From: Hiranmoy Das Chowdhury Date: Thu, 21 Nov 2024 14:28:02 +0600 Subject: [PATCH 04/29] autoScaler Signed-off-by: Hiranmoy Das Chowdhury --- docs/guides/pgbouncer/autoscaler/_index.md | 10 + .../pgbouncer/autoscaler/compute/_index.md | 10 + .../autoscaler/compute/compute-autoscale.md | 438 ++++++++++++++++++ .../pgbouncer/autoscaler/compute/overview.md | 55 +++ 4 files changed, 513 insertions(+) create mode 100644 docs/guides/pgbouncer/autoscaler/_index.md create mode 100644 docs/guides/pgbouncer/autoscaler/compute/_index.md create mode 100644 docs/guides/pgbouncer/autoscaler/compute/compute-autoscale.md create mode 100644 docs/guides/pgbouncer/autoscaler/compute/overview.md diff --git a/docs/guides/pgbouncer/autoscaler/_index.md b/docs/guides/pgbouncer/autoscaler/_index.md new file mode 100644 index 000000000..089e262d6 --- /dev/null +++ b/docs/guides/pgbouncer/autoscaler/_index.md @@ -0,0 +1,10 @@ +--- +title: Autoscaling +menu: + docs_{{ .version }}: + identifier: pb-auto-scaling + name: Autoscaling + parent: pb-pgbouncer-guides + weight: 46 +menu_name: docs_{{ .version }} +--- diff --git a/docs/guides/pgbouncer/autoscaler/compute/_index.md b/docs/guides/pgbouncer/autoscaler/compute/_index.md new file mode 100644 index 000000000..f7a179cf8 --- /dev/null +++ b/docs/guides/pgbouncer/autoscaler/compute/_index.md @@ -0,0 +1,10 @@ +--- +title: Compute Autoscaling +menu: + docs_{{ .version }}: + identifier: pb-compute-auto-scaling + name: Compute Autoscaling + parent: pp-auto-scaling + weight: 46 +menu_name: docs_{{ .version }} +--- diff --git a/docs/guides/pgbouncer/autoscaler/compute/compute-autoscale.md b/docs/guides/pgbouncer/autoscaler/compute/compute-autoscale.md new file mode 100644 index 000000000..ebc380681 --- /dev/null +++ b/docs/guides/pgbouncer/autoscaler/compute/compute-autoscale.md @@ -0,0 +1,438 @@ +--- +title: PgBouncer Autoscaling +menu: + docs_{{ .version }}: + identifier: pb-auto-scaling-pgbouncer + name: pgbouncerCompute + parent: pb-compute-auto-scaling + weight: 15 +menu_name: docs_{{ .version }} +section_menu_id: guides +--- + +> New to KubeDB? Please start [here](/docs/README.md). + +# Autoscaling the Compute Resource of a PgBouncer + +This guide will show you how to use `KubeDB` to autoscale compute resources i.e. cpu and memory of a PgBouncer. + +## Before You Begin + +- At first, you need to have a Kubernetes cluster, and the `kubectl` command-line tool must be configured to communicate with your cluster. + +- Install `KubeDB` Provisioner, Ops-manager and Autoscaler operator in your cluster following the steps [here](/docs/setup/README.md). + +- Install `Metrics Server` from [here](https://github.com/kubernetes-sigs/metrics-server#installation) + +- You should be familiar with the following `KubeDB` concepts: + - [PgBouncer](/docs/guides/pgbouncer/concepts/pgbouncer.md) + - [PgBouncerAutoscaler](/docs/guides/pgbouncer/concepts/autoscaler.md) + - [PgBouncerOpsRequest](/docs/guides/pgbouncer/concepts/opsrequest.md) + - [Compute Resource Autoscaling Overview](/docs/guides/pgbouncer/autoscaler/compute/overview.md) + +To keep everything isolated, we are going to use a separate namespace called `demo` throughout this tutorial. + +```bash +$ kubectl create ns demo +namespace/demo created +``` + +> **Note:** YAML files used in this tutorial are stored in [docs/examples/pgbouncer](/docs/examples/pgbouncer) directory of [kubedb/docs](https://github.com/kubedb/docs) repository. + +## Autoscaling of PgBouncer + +### Prepare Postgres +Prepare a KubeDB Postgres cluster using this [tutorial](/docs/guides/postgres/clustering/streaming_replication.md), or you can use any externally managed postgres but in that case you need to create an [appbinding](/docs/guides/pgbouncer/concepts/appbinding.md) yourself. In this tutorial we will use 3 node Postgres cluster named `ha-postgres`. + +Here, we are going to deploy a `PgBouncer` standalone using a supported version by `KubeDB` operator. Then we are going to apply `PgBouncerAutoscaler` to set up autoscaling. + +#### Deploy PgBouncer + +In this section, we are going to deploy a PgBouncer with version `4.5.0` Then, in the next section we will set up autoscaling for this pgbouncer using `PgBouncerAutoscaler` CRD. Below is the YAML of the `PgBouncer` CR that we are going to create, + +```yaml +apiVersion: kubedb.com/v1 +kind: PgBouncer +metadata: + name: pgbouncer-autoscale + namespace: demo +spec: + replicas: 1 + version: "1.18.0" + database: + syncUsers: true + databaseName: "postgres" + databaseRef: + name: "ha-postgres" + namespace: demo + connectionPool: + poolMode: session + port: 5432 + reservePoolSize: 5 + maxClientConnections: 87 + defaultPoolSize: 2 + minPoolSize: 1 + authType: md5 + deletionPolicy: WipeOut +``` + +Let's create the `PgBouncer` CRO we have shown above, + +```bash +$ kubectl create -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/pgbouncer/autoscaling/compute/pgbouncer-autoscale.yaml +pgbouncer.kubedb.com/pgbouncer-autoscale created +``` + +Now, wait until `pgbouncer-autoscale` has status `Ready`. i.e, + +```bash +$ kubectl get pb -n demo +NAME TYPE VERSION STATUS AGE +pgbouncer-autoscale kubedb.com/v1 1.18.0 Ready 22s +``` + +Let's check the Pod containers resources, + +```bash +$ kubectl get pod -n demo pgbouncer-autoscale-0 -o json | jq '.spec.containers[].resources' +{ + "limits": { + "cpu": "200m", + "memory": "300Mi" + }, + "requests": { + "cpu": "200m", + "memory": "300Mi" + } +} +``` + +Let's check the PgBouncer resources, +```bash +$ kubectl get pgbouncer -n demo pgbouncer-autoscale -o json | jq '.spec.podTemplate.spec.containers[0].resources' +{ + "limits": { + "cpu": "200m", + "memory": "300Mi" + }, + "requests": { + "cpu": "200m", + "memory": "300Mi" + } +} +``` + +You can see from the above outputs that the resources are same as the one we have assigned while deploying the pgbouncer. + +We are now ready to apply the `PgBouncerAutoscaler` CRO to set up autoscaling for this database. + +### Compute Resource Autoscaling + +Here, we are going to set up compute (cpu and memory) autoscaling using a PgBouncerAutoscaler Object. + +#### Create PgBouncerAutoscaler Object + +In order to set up compute resource autoscaling for this pgbouncer, we have to create a `PgBouncerAutoscaler` CRO with our desired configuration. Below is the YAML of the `PgBouncerAutoscaler` object that we are going to create, + +```yaml +apiVersion: autoscaling.kubedb.com/v1alpha1 +kind: PgBouncerAutoscaler +metadata: + name: pgbouncer-autoscale-ops + namespace: demo +spec: + databaseRef: + name: pgbouncer-autoscale + compute: + pgbouncer: + trigger: "On" + podLifeTimeThreshold: 5m + resourceDiffPercentage: 20 + minAllowed: + cpu: 400m + memory: 400Mi + maxAllowed: + cpu: 1 + memory: 1Gi + controlledResources: ["cpu", "memory"] + containerControlledValues: "RequestsAndLimits" +``` + +Here, + +- `spec.databaseRef.name` specifies that we are performing compute resource autoscaling on `pgbouncer-autoscale`. +- `spec.compute.pgbouncer.trigger` specifies that compute resource autoscaling is enabled for this pgbouncer. +- `spec.compute.pgbouncer.podLifeTimeThreshold` specifies the minimum lifetime for at least one of the pod to initiate a vertical scaling. +- `spec.compute.replicaset.resourceDiffPercentage` specifies the minimum resource difference in percentage. The default is 10%. + If the difference between current & recommended resource is less than ResourceDiffPercentage, Autoscaler Operator will ignore the updating. +- `spec.compute.pgbouncer.minAllowed` specifies the minimum allowed resources for this pgbouncer. +- `spec.compute.pgbouncer.maxAllowed` specifies the maximum allowed resources for this pgbouncer. +- `spec.compute.pgbouncer.controlledResources` specifies the resources that are controlled by the autoscaler. +- `spec.compute.pgbouncer.containerControlledValues` specifies which resource values should be controlled. The default is "RequestsAndLimits". +- `spec.opsRequestOptions` contains the options to pass to the created OpsRequest. It has 2 fields. Know more about them here : [timeout](/docs/guides/pgbouncer/concepts/opsrequest.md#spectimeout), [apply](/docs/guides/pgbouncer/concepts/opsrequest.md#specapply). + +Let's create the `PgBouncerAutoscaler` CR we have shown above, + +```bash +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/pgbouncer/autoscaling/compute/pgbouncer-autoscaler.yaml +pgbouncerautoscaler.autoscaling.kubedb.com/pgbouncer-autoscaler-ops created +``` + +#### Verify Autoscaling is set up successfully + +Let's check that the `pgbouncerautoscaler` resource is created successfully, + +```bash +$ kubectl get pgbouncerautoscaler -n demo +NAME AGE +pgbouncer-autoscale-ops 6m55s + +$ kubectl describe pgbouncerautoscaler pgbouncer-autoscale-ops -n demo +Name: pgbouncer-autoscale-ops +Namespace: demo +Labels: +Annotations: +API Version: autoscaling.kubedb.com/v1alpha1 +Kind: PgBouncerAutoscaler +Metadata: + Creation Timestamp: 2024-07-17T12:09:17Z + Generation: 1 + Resource Version: 81569 + UID: 3841c30b-3b19-4740-82f5-bf8e257ddc18 +Spec: + Compute: + PgBouncer: + Container Controlled Values: RequestsAndLimits + Controlled Resources: + cpu + memory + Max Allowed: + Cpu: 1 + Memory: 1Gi + Min Allowed: + Cpu: 400m + Memory: 400Mi + Pod Life Time Threshold: 5m0s + Resource Diff Percentage: 20 + Trigger: On + Database Ref: + Name: pgbouncer-autoscale + Ops Request Options: + Apply: IfReady +Status: + Checkpoints: + Cpu Histogram: + Bucket Weights: + Index: 0 + Weight: 10000 + Reference Timestamp: 2024-07-17T12:10:00Z + Total Weight: 0.8733542386168607 + First Sample Start: 2024-07-17T12:09:14Z + Last Sample Start: 2024-07-17T12:15:06Z + Last Update Time: 2024-07-17T12:15:38Z + Memory Histogram: + Bucket Weights: + Index: 11 + Weight: 10000 + Reference Timestamp: 2024-07-17T12:15:00Z + Total Weight: 0.7827734162991002 + Ref: + Container Name: pgbouncer + Vpa Object Name: pgbouncer-autoscale + Total Samples Count: 6 + Version: v3 + Conditions: + Last Transition Time: 2024-07-17T12:10:37Z + Message: Successfully created PgBouncerOpsRequest demo/pbops-pgbouncer-autoscale-zzell6 + Observed Generation: 1 + Reason: CreateOpsRequest + Status: True + Type: CreateOpsRequest + Vpas: + Conditions: + Last Transition Time: 2024-07-17T12:09:37Z + Status: True + Type: RecommendationProvided + Recommendation: + Container Recommendations: + Container Name: pgbouncer + Lower Bound: + Cpu: 400m + Memory: 400Mi + Target: + Cpu: 400m + Memory: 400Mi + Uncapped Target: + Cpu: 100m + Memory: 262144k + Upper Bound: + Cpu: 1 + Memory: 1Gi + Vpa Name: pgbouncer-autoscale +Events: +``` +So, the `pgbouncerautoscaler` resource is created successfully. + +you can see in the `Status.VPAs.Recommendation` section, that recommendation has been generated for our pgbouncer. Our autoscaler operator continuously watches the recommendation generated and creates an `pgbounceropsrequest` based on the recommendations, if the pgbouncer pods are needed to scaled up or down. + +Let's watch the `pgbounceropsrequest` in the demo namespace to see if any `pgbounceropsrequest` object is created. After some time you'll see that a `pgbounceropsrequest` will be created based on the recommendation. + +```bash +$ watch kubectl get pgbounceropsrequest -n demo +Every 2.0s: kubectl get pgbounceropsrequest -n demo +NAME TYPE STATUS AGE +pbops-pgbouncer-autoscale-zzell6 VerticalScaling Progressing 1m48s +``` + +Let's wait for the ops request to become successful. + +```bash +$ watch kubectl get pgbounceropsrequest -n demo +Every 2.0s: kubectl get pgbounceropsrequest -n demo +NAME TYPE STATUS AGE +pbops-pgbouncer-autoscale-zzell6 VerticalScaling Successful 3m40s +``` + +We can see from the above output that the `PgBouncerOpsRequest` has succeeded. If we describe the `PgBouncerOpsRequest` we will get an overview of the steps that were followed to scale the pgbouncer. + +```bash +$ kubectl describe pgbounceropsrequest -n demo pbops-pgbouncer-autoscale-zzell6 +Name: pbops-pgbouncer-autoscale-zzell6 +Namespace: demo +Labels: app.kubernetes.io/component=connection-pooler + app.kubernetes.io/instance=pgbouncer-autoscale + app.kubernetes.io/managed-by=kubedb.com + app.kubernetes.io/name=pgbouncers.kubedb.com +Annotations: +API Version: ops.kubedb.com/v1alpha1 +Kind: PgBouncerOpsRequest +Metadata: + Creation Timestamp: 2024-07-17T12:10:37Z + Generation: 1 + Owner References: + API Version: autoscaling.kubedb.com/v1alpha1 + Block Owner Deletion: true + Controller: true + Kind: PgBouncerAutoscaler + Name: pgbouncer-autoscale-ops + UID: 3841c30b-3b19-4740-82f5-bf8e257ddc18 + Resource Version: 81200 + UID: 57f99d31-af3d-4157-aa61-0f509ec89bbd +Spec: + Apply: IfReady + Database Ref: + Name: pgbouncer-autoscale + Type: VerticalScaling + Vertical Scaling: + Node: + Resources: + Limits: + Cpu: 400m + Memory: 400Mi + Requests: + Cpu: 400m + Memory: 400Mi +Status: + Conditions: + Last Transition Time: 2024-07-17T12:10:37Z + Message: PgBouncer ops-request has started to vertically scaling the PgBouncer nodes + Observed Generation: 1 + Reason: VerticalScaling + Status: True + Type: VerticalScaling + Last Transition Time: 2024-07-17T12:10:40Z + Message: Successfully paused database + Observed Generation: 1 + Reason: DatabasePauseSucceeded + Status: True + Type: DatabasePauseSucceeded + Last Transition Time: 2024-07-17T12:10:40Z + Message: Successfully updated PetSets Resources + Observed Generation: 1 + Reason: UpdatePetSets + Status: True + Type: UpdatePetSets + Last Transition Time: 2024-07-17T12:11:25Z + Message: Successfully Restarted Pods With Resources + Observed Generation: 1 + Reason: RestartPods + Status: True + Type: RestartPods + Last Transition Time: 2024-07-17T12:10:45Z + Message: get pod; ConditionStatus:True; PodName:pgbouncer-autoscale-0 + Observed Generation: 1 + Status: True + Type: GetPod--pgbouncer-autoscale-0 + Last Transition Time: 2024-07-17T12:10:45Z + Message: evict pod; ConditionStatus:True; PodName:pgbouncer-autoscale-0 + Observed Generation: 1 + Status: True + Type: EvictPod--pgbouncer-autoscale-0 + Last Transition Time: 2024-07-17T12:11:20Z + Message: check pod running; ConditionStatus:True; PodName:pgbouncer-autoscale-0 + Observed Generation: 1 + Status: True + Type: CheckPodRunning--pgbouncer-autoscale-0 + Last Transition Time: 2024-07-17T12:11:26Z + Message: Successfully completed the vertical scaling for PgBouncer + Observed Generation: 1 + Reason: Successful + Status: True + Type: Successful + Observed Generation: 1 + Phase: Successful +Events: + Type Reason Age From Message + ---- ------ ---- ---- ------- + Normal Starting 8m19s KubeDB Ops-manager Operator Start processing for PgBouncerOpsRequest: demo/pbops-pgbouncer-autoscale-zzell6 + Normal Starting 8m19s KubeDB Ops-manager Operator Pausing PgBouncer databse: demo/pgbouncer-autoscale + Normal Successful 8m19s KubeDB Ops-manager Operator Successfully paused PgBouncer database: demo/pgbouncer-autoscale for PgBouncerOpsRequest: pbops-pgbouncer-autoscale-zzell6 + Normal UpdatePetSets 8m16s KubeDB Ops-manager Operator Successfully updated PetSets Resources + Warning get pod; ConditionStatus:True; PodName:pgbouncer-autoscale-0 8m11s KubeDB Ops-manager Operator get pod; ConditionStatus:True; PodName:pgbouncer-autoscale-0 + Warning evict pod; ConditionStatus:True; PodName:pgbouncer-autoscale-0 8m11s KubeDB Ops-manager Operator evict pod; ConditionStatus:True; PodName:pgbouncer-autoscale-0 + Warning check pod running; ConditionStatus:False; PodName:pgbouncer-autoscale-0 8m6s KubeDB Ops-manager Operator check pod running; ConditionStatus:False; PodName:pgbouncer-autoscale-0 + Warning check pod running; ConditionStatus:True; PodName:pgbouncer-autoscale-0 7m36s KubeDB Ops-manager Operator check pod running; ConditionStatus:True; PodName:pgbouncer-autoscale-0 + Normal RestartPods 7m31s KubeDB Ops-manager Operator Successfully Restarted Pods With Resources + Normal Starting 7m31s KubeDB Ops-manager Operator Resuming PgBouncer database: demo/pgbouncer-autoscale + Normal Successful 7m30s KubeDB Ops-manager Operator Successfully resumed PgBouncer database: demo/pgbouncer-autoscale for PgBouncerOpsRequest: pbops-pgbouncer-autoscale-zzell6 +``` + +Now, we are going to verify from the Pod, and the PgBouncer yaml whether the resources of the pgbouncer has updated to meet up the desired state, Let's check, + +```bash +$ kubectl get pod -n demo pgbouncer-autoscale-0 -o json | jq '.spec.containers[].resources' +{ + "limits": { + "cpu": "400m", + "memory": "400Mi" + }, + "requests": { + "cpu": "400m", + "memory": "400Mi" + } +} + +$ kubectl get pgbouncer -n demo pgbouncer-autoscale -o json | jq '.spec.podTemplate.spec.containers[0].resources' +{ + "limits": { + "cpu": "400m", + "memory": "400Mi" + }, + "requests": { + "cpu": "400m", + "memory": "400Mi" + } +} +``` + + +The above output verifies that we have successfully auto-scaled the resources of the PgBouncer. + +## Cleaning Up + +To clean up the Kubernetes resources created by this tutorial, run: + +```bash +kubectl delete pb -n demo pgbouncer-autoscale +kubectl delete pgbouncerautoscaler -n demo pgbouncer-autoscale-ops +``` \ No newline at end of file diff --git a/docs/guides/pgbouncer/autoscaler/compute/overview.md b/docs/guides/pgbouncer/autoscaler/compute/overview.md new file mode 100644 index 000000000..87deb50da --- /dev/null +++ b/docs/guides/pgbouncer/autoscaler/compute/overview.md @@ -0,0 +1,55 @@ +--- +title: PgBouncer Compute Autoscaling Overview +menu: + docs_{{ .version }}: + identifier: pb-auto-scaling-overview + name: Overview + parent: pb-compute-auto-scaling + weight: 10 +menu_name: docs_{{ .version }} +section_menu_id: guides +--- + +> New to KubeDB? Please start [here](/docs/README.md). + +# PgBouncer Compute Resource Autoscaling + +This guide will give an overview on how KubeDB Autoscaler operator autoscales the database compute resources i.e. cpu and memory using `pgbouncerautoscaler` crd. + +## Before You Begin + +- You should be familiar with the following `KubeDB` concepts: + - [PgBouncer](/docs/guides/pgbouncer/concepts/pgbouncer.md) + - [PgBouncerAutoscaler](/docs/guides/pgbouncer/concepts/autoscaler.md) + - [PgBouncerOpsRequest](/docs/guides/pgbouncer/concepts/opsrequest.md) + +## How Compute Autoscaling Works + +The following diagram shows how KubeDB Autoscaler operator autoscales the resources of `PgBouncer`. Open the image in a new tab to see the enlarged version. + +
+  Compute Auto Scaling process of PgBouncer +
Fig: Compute Auto Scaling process of PgBouncer
+
+ +The Auto Scaling process consists of the following steps: + +1. At first, a user creates a `PgBouncer` Custom Resource Object (CRO). + +2. `KubeDB` Provisioner operator watches the `PgBouncer` CRO. + +3. When the operator finds a `PgBouncer` CRO, it creates `PetSet` and related necessary stuff like secrets, services, etc. + +4. Then, in order to set up autoscaling of `PgBouncer`, the user creates a `PgBouncerAutoscaler` CRO with desired configuration. + +5. `KubeDB` Autoscaler operator watches the `PgBouncerAutoscaler` CRO. + +6. `KubeDB` Autoscaler operator generates recommendation using the modified version of kubernetes [official recommender](https://github.com/kubernetes/autoscaler/tree/master/vertical-pod-autoscaler/pkg/recommender) for different components of the database, as specified in the `PgBouncerAutoscaler` CRO. + +7. If the generated recommendation doesn't match the current resources of the database, then `KubeDB` Autoscaler operator creates a `PgBouncerOpsRequest` CRO to scale the pgbouncer to match the recommendation generated. + +8. `KubeDB` Ops-manager operator watches the `PgBouncerOpsRequest` CRO. + +9. Then the `KubeDB` Ops-manager operator will scale the pgbouncer vertically as specified on the `PgBouncerOpsRequest` CRO. + +In the next docs, we are going to show a step-by-step guide on Autoscaling of PgBouncer using `PgBouncerAutoscaler` CRD. From 6b616b078e399734e9acf94587a799bb98dea710 Mon Sep 17 00:00:00 2001 From: Hiranmoy Das Chowdhury Date: Thu, 21 Nov 2024 17:14:05 +0600 Subject: [PATCH 05/29] reconfiguration Signed-off-by: Hiranmoy Das Chowdhury --- .../autoscaler/compute/compute-autoscale.md | 2 +- .../guides/pgbouncer/quickstart/quickstart.md | 6 +- docs/guides/pgbouncer/reconfigure/_index.md | 10 + docs/guides/pgbouncer/reconfigure/overview.md | 54 ++ .../reconfigure/reconfigure-pgbouncer.md | 771 ++++++++++++++++++ .../horizontal-scaling/horizontal-ops.md | 2 +- .../sync-users/sync-users-pgbouncer.md | 2 +- .../update-version/update_version.md | 4 +- 8 files changed, 843 insertions(+), 8 deletions(-) create mode 100644 docs/guides/pgbouncer/reconfigure/_index.md create mode 100644 docs/guides/pgbouncer/reconfigure/overview.md create mode 100644 docs/guides/pgbouncer/reconfigure/reconfigure-pgbouncer.md diff --git a/docs/guides/pgbouncer/autoscaler/compute/compute-autoscale.md b/docs/guides/pgbouncer/autoscaler/compute/compute-autoscale.md index ebc380681..da194603e 100644 --- a/docs/guides/pgbouncer/autoscaler/compute/compute-autoscale.md +++ b/docs/guides/pgbouncer/autoscaler/compute/compute-autoscale.md @@ -48,7 +48,7 @@ Here, we are going to deploy a `PgBouncer` standalone using a supported version #### Deploy PgBouncer -In this section, we are going to deploy a PgBouncer with version `4.5.0` Then, in the next section we will set up autoscaling for this pgbouncer using `PgBouncerAutoscaler` CRD. Below is the YAML of the `PgBouncer` CR that we are going to create, +In this section, we are going to deploy a PgBouncer with version `1.18.0` Then, in the next section we will set up autoscaling for this pgbouncer using `PgBouncerAutoscaler` CRD. Below is the YAML of the `PgBouncer` CR that we are going to create, ```yaml apiVersion: kubedb.com/v1 diff --git a/docs/guides/pgbouncer/quickstart/quickstart.md b/docs/guides/pgbouncer/quickstart/quickstart.md index 5a366ced9..8cdbceb6e 100644 --- a/docs/guides/pgbouncer/quickstart/quickstart.md +++ b/docs/guides/pgbouncer/quickstart/quickstart.md @@ -156,7 +156,7 @@ KubeDB implements a PgBouncer crd to define the specifications of a PgBouncer. Below is the PgBouncer object created in this tutorial. -`Note`: If your `KubeDB version` is less or equal to `v2024.6.4`, You have to use `v1alpha2` apiVersion. +`Note`: If your `KubeDB version` is less or equal to `v2024.6.4`, You have to use `v1` apiVersion. ```yaml apiVersion: kubedb.com/v1 @@ -186,7 +186,7 @@ pgbouncer.kubedb.com/pgbouncer-server created ``` ```yaml -apiVersion: kubedb.com/v1alpha2 +apiVersion: kubedb.com/v1 kind: PgBouncer metadata: name: pgbouncer-server @@ -208,7 +208,7 @@ spec: ``` ```bash -$ kubectl create -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/pgbouncer/quickstart/pgbouncer-server-v1alpha2.yaml +$ kubectl create -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/pgbouncer/quickstart/pgbouncer-server-v1.yaml pgbouncer.kubedb.com/pgbouncer-server created ``` diff --git a/docs/guides/pgbouncer/reconfigure/_index.md b/docs/guides/pgbouncer/reconfigure/_index.md new file mode 100644 index 000000000..9f613fd9a --- /dev/null +++ b/docs/guides/pgbouncer/reconfigure/_index.md @@ -0,0 +1,10 @@ +--- +title: Reconfigure +menu: + docs_{{ .version }}: + identifier: pb-reconfigure + name: Reconfigure + parent: pb-pgbouncer-guides + weight: 48 +menu_name: docs_{{ .version }} +--- \ No newline at end of file diff --git a/docs/guides/pgbouncer/reconfigure/overview.md b/docs/guides/pgbouncer/reconfigure/overview.md new file mode 100644 index 000000000..598acedb7 --- /dev/null +++ b/docs/guides/pgbouncer/reconfigure/overview.md @@ -0,0 +1,54 @@ +--- +title: Reconfiguring PgBouncer +menu: + docs_{{ .version }}: + identifier: pb-reconfigure-overview + name: Overview + parent: pb-reconfigure + weight: 10 +menu_name: docs_{{ .version }} +section_menu_id: guides +--- + +> New to KubeDB? Please start [here](/docs/README.md). + +# Reconfiguring PgBouncer + +This guide will give an overview on how KubeDB Ops-manager operator reconfigures `PgBouncer`. + +## Before You Begin + +- You should be familiar with the following `KubeDB` concepts: + - [PgBouncer](/docs/guides/pgbouncer/concepts/pgbouncer.md) + - [PgBouncerOpsRequest](/docs/guides/pgbouncer/concepts/opsrequest.md) + +## How Reconfiguring PgBouncer Process Works + +The following diagram shows how KubeDB Ops-manager operator reconfigures `PgBouncer`. Open the image in a new tab to see the enlarged version. + +
+  Reconfiguring process of PgBouncer +
Fig: Reconfiguring process of PgBouncer
+
+ +The Reconfiguring PgBouncer process consists of the following steps: + +1. At first, a user creates a `PgBouncer` Custom Resource (CR). + +2. `KubeDB` Provisioner operator watches the `PgBouncer` CR. + +3. When the operator finds a `PgBouncer` CR, it creates `PetSet` and related necessary stuff like secrets, services, etc. + +4. Then, in order to reconfigure of the `PgBouncer`, the user creates a `PgBouncerOpsRequest` CR with desired information. + +5. `KubeDB` Ops-manager operator watches the `PgBouncerOpsRequest` CR. + +6. When it finds a `PgBouncerOpsRequest` CR, it pauses the `PgBouncer` object which is referred from the `PgBouncerOpsRequest`. So, the `KubeDB` Provisioner operator doesn't perform any operations on the `PgBouncer` object during the reconfiguring process. + +7. Then the `KubeDB` Ops-manager operator will replace the existing configuration with the new configuration provided or merge the new configuration with the existing configuration according to the `PgBouncerOpsRequest` CR. + +8. Then the `KubeDB` Ops-manager operator will perform reload operation in each Pod so that the desired configuration will replace the old configuration. + +9. After the successful reconfiguring of the `PgBouncer`, the `KubeDB` Ops-manager operator resumes the `PgBouncer` object so that the `KubeDB` Provisioner operator resumes its usual operations. + +In the next docs, we are going to show a step-by-step guide on reconfiguring PgBouncer database components using `PgBouncerOpsRequest` CRD. \ No newline at end of file diff --git a/docs/guides/pgbouncer/reconfigure/reconfigure-pgbouncer.md b/docs/guides/pgbouncer/reconfigure/reconfigure-pgbouncer.md new file mode 100644 index 000000000..1aeda10ab --- /dev/null +++ b/docs/guides/pgbouncer/reconfigure/reconfigure-pgbouncer.md @@ -0,0 +1,771 @@ +--- +title: Reconfigure Standalone PgBouncer Database +menu: + docs_{{ .version }}: + identifier: pb-reconfigure-pgbouncer + name: PgBouncer Reconfigure + parent: pb-reconfigure + weight: 20 +menu_name: docs_{{ .version }} +section_menu_id: guides +--- + +> New to KubeDB? Please start [here](/docs/README.md). + +# Reconfigure PgBouncer + +This guide will show you how to use `KubeDB` Ops-manager operator to reconfigure a PgBouncer. + +## Before You Begin + +- At first, you need to have a Kubernetes cluster, and the `kubectl` command-line tool must be configured to communicate with your cluster. + +- Install `KubeDB` Provisioner and Ops-manager operator in your cluster following the steps [here](/docs/setup/README.md). + +- You should be familiar with the following `KubeDB` concepts: + - [PgBouncer](/docs/guides/pgbouncer/concepts/pgbouncer.md) + - [PgBouncerOpsRequest](/docs/guides/pgbouncer/concepts/opsrequest.md) + - [Reconfigure Overview](/docs/guides/pgbouncer/reconfigure/overview.md) + +To keep everything isolated, we are going to use a separate namespace called `demo` throughout this tutorial. + +```bash +$ kubectl create ns demo +namespace/demo created +``` + +> **Note:** YAML files used in this tutorial are stored in [docs/examples/pgbouncer](/docs/examples/pgbouncer) directory of [kubedb/docs](https://github.com/kubedb/docs) repository. + +### Prepare Postgres +For a PgBouncer surely we will need a Postgres server so, prepare a KubeDB Postgres cluster using this [tutorial](/docs/guides/postgres/clustering/streaming_replication.md), or you can use any externally managed postgres but in that case you need to create an [appbinding](/docs/guides/pgbouncer/concepts/appbinding.md) yourself. In this tutorial we will use 3 node Postgres cluster named `ha-postgres`. + +Now, we are going to deploy a `PgBouncer` using a supported version by `KubeDB` operator. Then we are going to apply `PgBouncerOpsRequest` to reconfigure its configuration. + +### Prepare PgBouncer + +Now, we are going to deploy a `PgBouncer` with version `1.18.0`. + +### Deploy PgBouncer + +At first, we will create `pgbouncer.ini` file containing required configuration settings. + +```ini +$ cat pgbouncer.ini +[pgbouncer] +auth_type = scram-sha-256 +``` +Here, `auth_type` is set to `scram-sha-256`, whereas the default value is `md5`. + +Now, we will create a secret with this configuration file. + +```bash +$ kubectl create secret generic -n demo pb-custom-config --from-file=./pgbouncer.ini +secret/pb-custom-config created +``` + +In this section, we are going to create a PgBouncer object specifying `spec.configSecret` field to apply this custom configuration. Below is the YAML of the `PgBouncer` CR that we are going to create, + +```yaml +apiVersion: kubedb.com/v1 +kind: PgBouncer +metadata: + name: pb-custom + namespace: demo +spec: + replicas: 1 + version: "1.18.0" + database: + syncUsers: true + databaseName: "postgres" + databaseRef: + name: "ha-postgres" + namespace: demo + connectionPool: + poolMode: session + port: 5432 + reservePoolSize: 5 + maxClientConnections: 87 + defaultPoolSize: 2 + minPoolSize: 1 + deletionPolicy: WipeOut +``` + +Let's create the `PgBouncer` CR we have shown above, + +```bash +$ kubectl create -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/pgbouncer/reconfigure/pb-custom-config.yaml +pgbouncer.kubedb.com/pb-custom created +``` + +Now, wait until `pb-custom` has status `Ready`. i.e, + +```bash +$ kubectl get pb -n demo +NAME TYPE VERSION STATUS AGE +pb-custom kubedb.com/v1 1.18.0 Ready 112s +``` + +Now, we will check if the pgbouncer has started with the custom configuration we have provided. + +Now, you can exec into the pgbouncer pod and find if the custom configuration is there, + +```bash +$ kubectl exec -it -n demo pb-custom-0 -- bash +pb-custom-0:/$ cat opt/pgbouncer/etc/pgbouncer.ini +backend_hostname0 = 'ha-postgres.demo.svc' +backend_port0 = 5432 +backend_weight0 = 1 +backend_flag0 = 'ALWAYS_PRIMARY|DISALLOW_TO_FAILOVER' +backend_hostname1 = 'ha-postgres-standby.demo.svc' +backend_port1 = 5432 +backend_weight1 = 1 +backend_flag1 = 'DISALLOW_TO_FAILOVER' +max_pool = 60 +enable_pool_hba = on +listen_addresses = * +port = 9999 +socket_dir = '/var/run/pgbouncer' +pcp_listen_addresses = * +pcp_port = 9595 +pcp_socket_dir = '/var/run/pgbouncer' +log_per_node_statement = on +sr_check_period = 0 +health_check_period = 0 +backend_clustering_mode = 'streaming_replication' +num_init_children = 5 +child_life_time = 300 +child_max_connections = 0 +connection_life_time = 0 +client_idle_limit = 0 +connection_cache = on +load_balance_mode = on +ssl = 'off' +failover_on_backend_error = 'off' +log_min_messages = 'warning' +statement_level_load_balance = 'off' +memory_cache_enabled = 'off' +memqcache_oiddir = '/tmp/oiddir/' +allow_clear_text_frontend_auth = 'false' +failover_on_backend_error = 'off' +pb-custom-0:/$ exit +exit +``` + +As we can see from the configuration of running pgbouncer, the value of `auth_type` has been set to `scram-sha-256`. + +### Reconfigure using new secret + +Now we will reconfigure this pgbouncer to set `auth_type` to `md5`. + +Now, we will edit the `pgbouncer.ini` file containing required configuration settings. + +```ini +$ cat pgbouncer.ini +auth_type=md5 +``` + +Then, we will create a new secret with this configuration file. + +```bash +$ kubectl create secret generic -n demo new-custom-config --from-file=./pgbouncer.ini +secret/new-custom-config created +``` + +#### Create PgBouncerOpsRequest + +Now, we will use this secret to replace the previous secret using a `PgBouncerOpsRequest` CR. The `PgBouncerOpsRequest` yaml is given below, + +```yaml +apiVersion: ops.kubedb.com/v1alpha1 +kind: PgBouncerOpsRequest +metadata: + name: pbops-reconfigure + namespace: demo +spec: + type: Reconfigure + databaseRef: + name: pb-custom + configuration: + pgbouncer: + configSecret: + name: new-custom-config + timeout: 5m + apply: IfReady +``` + +Here, + +- `spec.databaseRef.name` specifies that we are reconfiguring `pb-csutom` pgbouncer. +- `spec.type` specifies that we are performing `Reconfigure` on our pgbouncer. +- `spec.configuration.pgbouncer.configSecret.name` specifies the name of the new secret. +- Have a look [here](/docs/guides/pgbouncer/concepts/opsrequest.md#spectimeout) on the respective sections to understand the `timeout` & `apply` fields. + +Let's create the `PgBouncerOpsRequest` CR we have shown above, + +```bash +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/pgbouncer/reconfigure/pbops-reconfigure.yaml +pgbounceropsrequest.ops.kubedb.com/pbops-reconfigure created +``` + +#### Verify the new configuration is working + +If everything goes well, `KubeDB` Ops-manager operator will update the `configSecret` of `PgBouncer` object. + +Let's wait for `PgBouncerOpsRequest` to be `Successful`. Run the following command to watch `PgBouncerOpsRequest` CR, + +```bash +$ watch kubectl get pgbounceropsrequest -n demo +Every 2.0s: kubectl get pgbounceropsrequest -n demo +NAME TYPE STATUS AGE +pbops-reconfigure Reconfigure Successful 63s +``` + +We can see from the above output that the `PgBouncerOpsRequest` has succeeded. If we describe the `PgBouncerOpsRequest` we will get an overview of the steps that were followed to reconfigure the pgbouncer. + +```bash +$ kubectl describe pgbounceropsrequest -n demo pbops-reconfigure +Name: pbops-reconfigure +Namespace: demo +Labels: +Annotations: +API Version: ops.kubedb.com/v1alpha1 +Kind: PgBouncerOpsRequest +Metadata: + Creation Timestamp: 2024-07-30T05:42:56Z + Generation: 1 + Resource Version: 95239 + UID: 54a12624-048c-49a6-b852-6286da587535 +Spec: + Apply: IfReady + Configuration: + Config Secret: + Name: new-custom-config + Database Ref: + Name: pb-custom + Timeout: 5m + Type: Reconfigure +Status: + Conditions: + Last Transition Time: 2024-07-30T05:42:56Z + Message: PgBouncer ops-request has started to `Reconfigure` the PgBouncer nodes + Observed Generation: 1 + Reason: Reconfigure + Status: True + Type: Reconfigure + Last Transition Time: 2024-07-30T05:42:59Z + Message: Successfully paused database + Observed Generation: 1 + Reason: DatabasePauseSucceeded + Status: True + Type: DatabasePauseSucceeded + Last Transition Time: 2024-07-30T05:43:00Z + Message: Successfully updated PetSet + Observed Generation: 1 + Reason: UpdatePetSets + Status: True + Type: UpdatePetSets + Last Transition Time: 2024-07-30T05:43:00Z + Message: Successfully updated PgBouncer + Observed Generation: 1 + Reason: UpdateDatabase + Status: True + Type: UpdateDatabase + Last Transition Time: 2024-07-30T05:43:45Z + Message: Successfully Restarted Pods With Resources + Observed Generation: 1 + Reason: RestartPods + Status: True + Type: RestartPods + Last Transition Time: 2024-07-30T05:43:05Z + Message: get pod; ConditionStatus:True; PodName:pb-custom-0 + Observed Generation: 1 + Status: True + Type: GetPod--pb-custom-0 + Last Transition Time: 2024-07-30T05:43:05Z + Message: evict pod; ConditionStatus:True; PodName:pb-custom-0 + Observed Generation: 1 + Status: True + Type: EvictPod--pb-custom-0 + Last Transition Time: 2024-07-30T05:43:40Z + Message: check pod running; ConditionStatus:True; PodName:pb-custom-0 + Observed Generation: 1 + Status: True + Type: CheckPodRunning--pb-custom-0 + Last Transition Time: 2024-07-30T05:43:45Z + Message: Successfully completed the reconfigure for PgBouncer + Observed Generation: 1 + Reason: Successful + Status: True + Type: Successful + Observed Generation: 1 + Phase: Successful +Events: + Type Reason Age From Message + ---- ------ ---- ---- ------- + Normal Starting 100s KubeDB Ops-manager Operator Start processing for PgBouncerOpsRequest: demo/pbops-reconfigure + Normal Starting 100s KubeDB Ops-manager Operator Pausing PgBouncer databse: demo/pb-custom + Normal Successful 100s KubeDB Ops-manager Operator Successfully paused PgBouncer database: demo/pb-custom for PgBouncerOpsRequest: pbops-reconfigure + Normal UpdatePetSets 96s KubeDB Ops-manager Operator Successfully updated PetSet + Normal UpdatePetSets 96s KubeDB Ops-manager Operator Successfully updated PetSet + Normal UpdateDatabase 96s KubeDB Ops-manager Operator Successfully updated PgBouncer + Warning get pod; ConditionStatus:True; PodName:pb-custom-0 91s KubeDB Ops-manager Operator get pod; ConditionStatus:True; PodName:pb-custom-0 + Warning evict pod; ConditionStatus:True; PodName:pb-custom-0 91s KubeDB Ops-manager Operator evict pod; ConditionStatus:True; PodName:pb-custom-0 + Warning check pod running; ConditionStatus:False; PodName:pb-custom-0 86s KubeDB Ops-manager Operator check pod running; ConditionStatus:False; PodName:pb-custom-0 + Warning check pod running; ConditionStatus:True; PodName:pb-custom-0 56s KubeDB Ops-manager Operator check pod running; ConditionStatus:True; PodName:pb-custom-0 + Normal RestartPods 51s KubeDB Ops-manager Operator Successfully Restarted Pods With Resources + Normal Starting 51s KubeDB Ops-manager Operator Resuming PgBouncer database: demo/pb-custom + Normal Successful 51s KubeDB Ops-manager Operator Successfully resumed PgBouncer database: demo/pb-custom for PgBouncerOpsRequest: pbops-reconfigure +``` + +Now let's exec into the pgbouncer pod and check the new configuration we have provided. + +```bash +$ kubectl exec -it -n demo pb-custom-0 -- bash +pb-custom-0:/$ cat opt/pgbouncer/etc/pgbouncer.ini +backend_hostname0 = 'ha-postgres.demo.svc' +backend_port0 = 5432 +backend_weight0 = 1 +backend_flag0 = 'ALWAYS_PRIMARY|DISALLOW_TO_FAILOVER' +backend_hostname1 = 'ha-postgres-standby.demo.svc' +backend_port1 = 5432 +backend_weight1 = 1 +backend_flag1 = 'DISALLOW_TO_FAILOVER' +listen_addresses = * +log_per_node_statement = on +num_init_children = 5 +max_pool = 50 +child_life_time = '300' +child_max_connections = 0 +connection_life_time = 0 +client_idle_limit = 0 +connection_cache = on +load_balance_mode = on +log_min_messages = 'warning' +statement_level_load_balance = 'off' +memory_cache_enabled = 'off' +enable_pool_hba = on +port = 9999 +socket_dir = '/var/run/pgbouncer' +pcp_listen_addresses = * +pcp_port = 9595 +pcp_socket_dir = '/var/run/pgbouncer' +sr_check_period = 0 +health_check_period = 0 +backend_clustering_mode = 'streaming_replication' +ssl = 'off' +failover_on_backend_error = 'off' +memqcache_oiddir = '/tmp/oiddir/' +allow_clear_text_frontend_auth = 'false' +failover_on_backend_error = 'off' +pb-custom-0:/$ exit +exit +``` + +As we can see from the configuration of running pgbouncer, the value of `auth_type` has been changed from `scram-sha-256` to `md5`. So the reconfiguration of the pgbouncer is successful. + + +### Reconfigure using apply config + +Now we will reconfigure this pgbouncer again to set `auth_type` to `scram-sha-256`. This time we won't use a new secret. We will use the `applyConfig` field of the `PgBouncerOpsRequest`. This will merge the new config in the existing secret. + +#### Create PgBouncerOpsRequest + +Now, we will use the new configuration in the `data` field in the `PgBouncerOpsRequest` CR. The `PgBouncerOpsRequest` yaml is given below, + +```yaml +apiVersion: ops.kubedb.com/v1alpha1 +kind: PgBouncerOpsRequest +metadata: + name: pbops-reconfigure-apply + namespace: demo +spec: + type: Reconfigure + databaseRef: + name: pb-custom + configuration: + pgbouncer: + applyConfig: + pgbouncer.ini: |- + [pgbouncer] + auth_type=scram-sha-256 + timeout: 5m + apply: IfReady +``` + +Here, + +- `spec.databaseRef.name` specifies that we are reconfiguring `pb-custom` pgbouncer. +- `spec.type` specifies that we are performing `Reconfigure` on our pgbouncer. +- `spec.configuration.pgbouncer.applyConfig` specifies the new configuration that will be merged in the existing secret. + +Let's create the `PgBouncerOpsRequest` CR we have shown above, + +```bash +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/pgbouncer/reconfigure/pbops-reconfigure-apply.yaml +pgbounceropsrequest.ops.kubedb.com/pbops-reconfigure-apply created +``` + +#### Verify the new configuration is working + +If everything goes well, `KubeDB` Ops-manager operator will merge this new config with the existing configuration. + +Let's wait for `PgBouncerOpsRequest` to be `Successful`. Run the following command to watch `PgBouncerOpsRequest` CR, + +```bash +$ watch kubectl get pgbounceropsrequest -n demo +Every 2.0s: kubectl get pgbounceropsrequest -n demo +NAME TYPE STATUS AGE +pbops-reconfigure Reconfigure Successful 9m15s +pbops-reconfigure-apply Reconfigure Successful 53s +``` + +We can see from the above output that the `PgBouncerOpsRequest` has succeeded. If we describe the `PgBouncerOpsRequest` we will get an overview of the steps that were followed to reconfigure the pgbouncer. + +```bash +$ kubectl describe pgbounceropsrequest -n demo pbops-reconfigure-apply +Name: pbops-reconfigure-apply +Namespace: demo +Labels: +Annotations: +API Version: ops.kubedb.com/v1alpha1 +Kind: PgBouncerOpsRequest +Metadata: + Creation Timestamp: 2024-07-30T05:51:18Z + Generation: 1 + Resource Version: 95874 + UID: 92b0f18c-a329-4bb7-85d0-ef66f32bf57a +Spec: + Apply: IfReady + Configuration: + Apply Config: + pgbouncer.ini: max_pool = 75 + Database Ref: + Name: pb-custom + Timeout: 5m + Type: Reconfigure +Status: + Conditions: + Last Transition Time: 2024-07-30T05:51:18Z + Message: PgBouncer ops-request has started to `Reconfigure` the PgBouncer nodes + Observed Generation: 1 + Reason: Reconfigure + Status: True + Type: Reconfigure + Last Transition Time: 2024-07-30T05:51:21Z + Message: Successfully paused database + Observed Generation: 1 + Reason: DatabasePauseSucceeded + Status: True + Type: DatabasePauseSucceeded + Last Transition Time: 2024-07-30T05:51:21Z + Message: Successfully updated PetSet + Observed Generation: 1 + Reason: UpdatePetSets + Status: True + Type: UpdatePetSets + Last Transition Time: 2024-07-30T05:51:22Z + Message: Successfully updated PgBouncer + Observed Generation: 1 + Reason: UpdateDatabase + Status: True + Type: UpdateDatabase + Last Transition Time: 2024-07-30T05:52:07Z + Message: Successfully Restarted Pods With Resources + Observed Generation: 1 + Reason: RestartPods + Status: True + Type: RestartPods + Last Transition Time: 2024-07-30T05:51:27Z + Message: get pod; ConditionStatus:True; PodName:pb-custom-0 + Observed Generation: 1 + Status: True + Type: GetPod--pb-custom-0 + Last Transition Time: 2024-07-30T05:51:27Z + Message: evict pod; ConditionStatus:True; PodName:pb-custom-0 + Observed Generation: 1 + Status: True + Type: EvictPod--pb-custom-0 + Last Transition Time: 2024-07-30T05:52:02Z + Message: check pod running; ConditionStatus:True; PodName:pb-custom-0 + Observed Generation: 1 + Status: True + Type: CheckPodRunning--pb-custom-0 + Last Transition Time: 2024-07-30T05:52:07Z + Message: Successfully completed the reconfigure for PgBouncer + Observed Generation: 1 + Reason: Successful + Status: True + Type: Successful + Observed Generation: 1 + Phase: Successful +Events: + Type Reason Age From Message + ---- ------ ---- ---- ------- + Normal Starting 77s KubeDB Ops-manager Operator Start processing for PgBouncerOpsRequest: demo/pbops-reconfigure-apply + Normal Starting 77s KubeDB Ops-manager Operator Pausing PgBouncer databse: demo/pb-custom + Normal Successful 77s KubeDB Ops-manager Operator Successfully paused PgBouncer database: demo/pb-custom for PgBouncerOpsRequest: pbops-reconfigure-apply + Normal UpdatePetSets 74s KubeDB Ops-manager Operator Successfully updated PetSet + Normal UpdatePetSets 73s KubeDB Ops-manager Operator Successfully updated PetSet + Normal UpdateDatabase 73s KubeDB Ops-manager Operator Successfully updated PgBouncer + Warning get pod; ConditionStatus:True; PodName:pb-custom-0 68s KubeDB Ops-manager Operator get pod; ConditionStatus:True; PodName:pb-custom-0 + Warning evict pod; ConditionStatus:True; PodName:pb-custom-0 68s KubeDB Ops-manager Operator evict pod; ConditionStatus:True; PodName:pb-custom-0 + Warning check pod running; ConditionStatus:False; PodName:pb-custom-0 63s KubeDB Ops-manager Operator check pod running; ConditionStatus:False; PodName:pb-custom-0 + Warning check pod running; ConditionStatus:True; PodName:pb-custom-0 33s KubeDB Ops-manager Operator check pod running; ConditionStatus:True; PodName:pb-custom-0 + Normal RestartPods 28s KubeDB Ops-manager Operator Successfully Restarted Pods With Resources + Normal Starting 28s KubeDB Ops-manager Operator Resuming PgBouncer database: demo/pb-custom + Normal Successful 28s KubeDB Ops-manager Operator Successfully resumed PgBouncer database: demo/pb-custom for PgBouncerOpsRequest: pbops-reconfigure-apply +``` + +Now let's exec into the pgbouncer pod and check the new configuration we have provided. + +```bash +$ kubectl exec -it -n demo pb-custom-0 -- bash +pb-custom-0:/$ cat opt/pgbouncer/etc/pgbouncer.ini +memory_cache_enabled = 'off' +num_init_children = 5 +pcp_socket_dir = '/var/run/pgbouncer' +port = '9999' +enable_pool_hba = on +log_min_messages = 'warning' +pcp_port = '9595' +sr_check_period = 0 +ssl = 'off' +backend_weight1 = 1 +load_balance_mode = on +backend_weight0 = 1 +backend_port0 = '5432' +connection_cache = on +backend_hostname1 = 'ha-postgres-standby.demo.svc' +health_check_period = 0 +memqcache_oiddir = '/tmp/oiddir/' +statement_level_load_balance = 'off' +allow_clear_text_frontend_auth = 'false' +log_per_node_statement = on +backend_hostname0 = 'ha-postgres.demo.svc' +backend_flag1 = 'DISALLOW_TO_FAILOVER' +listen_addresses = * +failover_on_backend_error = 'off' +pcp_listen_addresses = * +child_max_connections = 0 +socket_dir = '/var/run/pgbouncer' +backend_flag0 = 'ALWAYS_PRIMARY|DISALLOW_TO_FAILOVER' +backend_port1 = '5432' +backend_clustering_mode = 'streaming_replication' +connection_life_time = 0 +child_life_time = '300' +max_pool = 75 +client_idle_limit = 0 +failover_on_backend_error = 'off' +pb-custom-0:/$ exit +exit +``` + +As we can see from the configuration of running pgbouncer, the value of `auth_type` has been changed from `md5` to `scram-sha-256`. So the reconfiguration of the pgbouncer using the `applyConfig` field is successful. + + +### Remove config + +Now we will reconfigure this pgbouncer to remove the custom config provided and get it back to the default config. We will use the `removeCustomConfig` field of the `PgBouncerOpsRequest`. This will remove all the custom config provided and get the pgbouncer back to the default config. + +#### Create PgBouncerOpsRequest + +Now, we will use the `removeCustomConfig` field in the `PgBouncerOpsRequest` CR. The `PgBouncerOpsRequest` yaml is given below, + +```yaml +apiVersion: ops.kubedb.com/v1alpha1 +kind: PgBouncerOpsRequest +metadata: + name: pbops-reconfigure-remove + namespace: demo +spec: + type: Reconfigure + databaseRef: + name: pb-custom + configuration: + pgbouncer: + removeCustomConfig: true + timeout: 5m + apply: IfReady +``` + +Here, + +- `spec.databaseRef.name` specifies that we are reconfiguring `pb-custom` pgbouncer. +- `spec.type` specifies that we are performing `Reconfigure` on our pgbouncer. +- `spec.configuration.pgbouncer.removeCustomConfig` specifies for boolean values to remove custom configuration. + +Let's create the `PgBouncerOpsRequest` CR we have shown above, + +```bash +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/pgbouncer/reconfigure/pbops-reconfigure-remove.yaml +pgbounceropsrequest.ops.kubedb.com/pbops-reconfigure-remove created +``` + +#### Verify if the configuration is removed + +If everything goes well, `KubeDB` Ops-manager operator will remove the custom configuration and move back to the default configuration. + +Let's wait for `PgBouncerOpsRequest` to be `Successful`. Run the following command to watch `PgBouncerOpsRequest` CR, + +```bash +$ watch kubectl get pgbounceropsrequest -n demo +Every 2.0s: kubectl get pgbounceropsrequest -n demo +kubectl get pgbounceropsrequest -n demo +NAME TYPE STATUS AGE +pbops-reconfigure Reconfigure Successful 71m +pbops-reconfigure-apply Reconfigure Successful 63m +pbops-reconfigure-remove Reconfigure Successful 57s +``` + +We can see from the above output that the `PgBouncerOpsRequest` has succeeded. If we describe the `PgBouncerOpsRequest` we will get an overview of the steps that were followed to reconfigure the pgbouncer. + +```bash +$ kubectl describe pgbounceropsrequest -n demo pbops-reconfigure-remove +Name: pbops-reconfigure-remove +Namespace: demo +Labels: +Annotations: +API Version: ops.kubedb.com/v1alpha1 +Kind: PgBouncerOpsRequest +Metadata: + Creation Timestamp: 2024-07-30T06:53:27Z + Generation: 1 + Resource Version: 99827 + UID: 24c9cba3-5e85-40dc-96f3-373d2dd7a8ba +Spec: + Apply: IfReady + Configuration: + Remove Custom Config: true + Database Ref: + Name: pb-custom + Timeout: 5m + Type: Reconfigure +Status: + Conditions: + Last Transition Time: 2024-07-30T06:53:28Z + Message: PgBouncer ops-request has started to `Reconfigure` the PgBouncer nodes + Observed Generation: 1 + Reason: Reconfigure + Status: True + Type: Reconfigure + Last Transition Time: 2024-07-30T06:53:31Z + Message: Successfully paused database + Observed Generation: 1 + Reason: DatabasePauseSucceeded + Status: True + Type: DatabasePauseSucceeded + Last Transition Time: 2024-07-30T06:53:31Z + Message: Successfully updated PetSet + Observed Generation: 1 + Reason: UpdatePetSets + Status: True + Type: UpdatePetSets + Last Transition Time: 2024-07-30T06:53:32Z + Message: Successfully updated PgBouncer + Observed Generation: 1 + Reason: UpdateDatabase + Status: True + Type: UpdateDatabase + Last Transition Time: 2024-07-30T06:54:17Z + Message: Successfully Restarted Pods With Resources + Observed Generation: 1 + Reason: RestartPods + Status: True + Type: RestartPods + Last Transition Time: 2024-07-30T06:53:37Z + Message: get pod; ConditionStatus:True; PodName:pb-custom-0 + Observed Generation: 1 + Status: True + Type: GetPod--pb-custom-0 + Last Transition Time: 2024-07-30T06:53:37Z + Message: evict pod; ConditionStatus:True; PodName:pb-custom-0 + Observed Generation: 1 + Status: True + Type: EvictPod--pb-custom-0 + Last Transition Time: 2024-07-30T06:54:12Z + Message: check pod running; ConditionStatus:True; PodName:pb-custom-0 + Observed Generation: 1 + Status: True + Type: CheckPodRunning--pb-custom-0 + Last Transition Time: 2024-07-30T06:54:17Z + Message: Successfully completed the reconfigure for PgBouncer + Observed Generation: 1 + Reason: Successful + Status: True + Type: Successful + Observed Generation: 1 + Phase: Successful +Events: + Type Reason Age From Message + ---- ------ ---- ---- ------- + Normal Starting 74s KubeDB Ops-manager Operator Start processing for PgBouncerOpsRequest: demo/pbops-reconfigure-remove + Normal Starting 74s KubeDB Ops-manager Operator Pausing PgBouncer databse: demo/pb-custom + Normal Successful 74s KubeDB Ops-manager Operator Successfully paused PgBouncer database: demo/pb-custom for PgBouncerOpsRequest: pbops-reconfigure-remove + Normal UpdatePetSets 71s KubeDB Ops-manager Operator Successfully updated PetSet + Normal UpdatePetSets 70s KubeDB Ops-manager Operator Successfully updated PetSet + Normal UpdateDatabase 70s KubeDB Ops-manager Operator Successfully updated PgBouncer + Warning get pod; ConditionStatus:True; PodName:pb-custom-0 65s KubeDB Ops-manager Operator get pod; ConditionStatus:True; PodName:pb-custom-0 + Warning evict pod; ConditionStatus:True; PodName:pb-custom-0 65s KubeDB Ops-manager Operator evict pod; ConditionStatus:True; PodName:pb-custom-0 + Warning check pod running; ConditionStatus:False; PodName:pb-custom-0 60s KubeDB Ops-manager Operator check pod running; ConditionStatus:False; PodName:pb-custom-0 + Warning check pod running; ConditionStatus:True; PodName:pb-custom-0 30s KubeDB Ops-manager Operator check pod running; ConditionStatus:True; PodName:pb-custom-0 + Normal RestartPods 25s KubeDB Ops-manager Operator Successfully Restarted Pods With Resources + Normal Starting 25s KubeDB Ops-manager Operator Resuming PgBouncer database: demo/pb-custom + Normal Successful 25s KubeDB Ops-manager Operator Successfully resumed PgBouncer database: demo/pb-custom for PgBouncerOpsRequest: pbops-reconfigure-remove +``` + +Now let's exec into the pgbouncer pod and check the configuration. + +```bash +$ kubectl exec -it -n demo pb-custom-0 -- bash +pb-custom-0:/$ cat opt/pgbouncer/etc/pgbouncer.ini +backend_hostname0 = 'ha-postgres.demo.svc' +backend_port0 = 5432 +backend_weight0 = 1 +backend_flag0 = 'ALWAYS_PRIMARY|DISALLOW_TO_FAILOVER' +backend_hostname1 = 'ha-postgres-standby.demo.svc' +backend_port1 = 5432 +backend_weight1 = 1 +backend_flag1 = 'DISALLOW_TO_FAILOVER' +enable_pool_hba = on +listen_addresses = * +port = 9999 +socket_dir = '/var/run/pgbouncer' +pcp_listen_addresses = * +pcp_port = 9595 +pcp_socket_dir = '/var/run/pgbouncer' +log_per_node_statement = on +sr_check_period = 0 +health_check_period = 0 +backend_clustering_mode = 'streaming_replication' +num_init_children = 5 +max_pool = 15 +child_life_time = 300 +child_max_connections = 0 +connection_life_time = 0 +client_idle_limit = 0 +connection_cache = on +load_balance_mode = on +ssl = 'off' +failover_on_backend_error = 'off' +log_min_messages = 'warning' +statement_level_load_balance = 'off' +memory_cache_enabled = 'off' +memqcache_oiddir = '/tmp/oiddir/' +allow_clear_text_frontend_auth = 'false' +failover_on_backend_error = 'off' +pb-custom-0:/$ exit +exit +``` + +As we can see from the configuration of running pgbouncer, the value of `auth_type` has been changed from `scram-sha-256` to `md5`. So the reconfiguration of the pgbouncer using the `removeCustomConfig` field is successful. + + +## Cleaning Up + +To clean up the Kubernetes resources created by this tutorial, run: +```bash +kubectl delete -n demo pb/pb-custom +kubectl delete pgbounceropsrequest -n demo pbops-reconfigure pbops-reconfigure-apply pbops-reconfigure-remove +kubectl delete pg -n demo ha-postgres +kubectl delete ns demo +``` \ No newline at end of file diff --git a/docs/guides/pgbouncer/scaling/horizontal-scaling/horizontal-ops.md b/docs/guides/pgbouncer/scaling/horizontal-scaling/horizontal-ops.md index 67f590614..06491c627 100644 --- a/docs/guides/pgbouncer/scaling/horizontal-scaling/horizontal-ops.md +++ b/docs/guides/pgbouncer/scaling/horizontal-scaling/horizontal-ops.md @@ -88,7 +88,7 @@ Now, wait until `pb-horizontal ` has status `Ready`. i.e, ```bash $ kubectl get pb -n demo NAME TYPE VERSION STATUS AGE -pb-horizontal kubedb.com/v1alpha2 4.5.0 Ready 2m +pb-horizontal kubedb.com/v1 1.18.0 Ready 2m ``` Let's check the number of replicas this pgbouncer has from the PgBouncer object, number of pods the petset have, diff --git a/docs/guides/pgbouncer/sync-users/sync-users-pgbouncer.md b/docs/guides/pgbouncer/sync-users/sync-users-pgbouncer.md index 641fadfc0..77cb73026 100644 --- a/docs/guides/pgbouncer/sync-users/sync-users-pgbouncer.md +++ b/docs/guides/pgbouncer/sync-users/sync-users-pgbouncer.md @@ -101,7 +101,7 @@ Now, wait until `pgbouncer-sync` has status `Ready`. i.e, ```bash $ kubectl get pb -n demo NAME TYPE VERSION STATUS AGE -pgbouncer-sync kubedb.com/v1 4.5.0 Ready 41s +pgbouncer-sync kubedb.com/v1 1.18.0 Ready 41s ``` ### Sync Users diff --git a/docs/guides/pgbouncer/update-version/update_version.md b/docs/guides/pgbouncer/update-version/update_version.md index 127a2ec40..5cc3d7ed4 100644 --- a/docs/guides/pgbouncer/update-version/update_version.md +++ b/docs/guides/pgbouncer/update-version/update_version.md @@ -160,7 +160,7 @@ Spec: Name: pb-update Type: UpdateVersion Update Version: - Target Version: 4.5.0 + Target Version: 1.18.0 Status: Conditions: Last Transition Time: 2024-07-17T06:31:58Z @@ -242,7 +242,7 @@ $ kubectl get petset -n demo pb-update -o=jsonpath='{.spec.template.spec.contain mongo:4.0.5 $ kubectl get pods -n demo mg-standalone-0 -o=jsonpath='{.spec.containers[0].image}{"\n"}' -ghcr.io/appscode-images/pgbouncer2:4.5.0@sha256:2697fcad9e11bdc704f6ae0fba85c4451c6b0243140aaaa33e719c3af548bda1 +ghcr.io/appscode-images/pgbouncer:1.23.1@sha256:2697fcad9e11bdc704f6ae0fba85c4451c6b0243140aaaa33e719c3af548bda1 ``` You can see from above, our `PgBouncer` has been updated with the new version. So, the update process is successfully completed. From 2ebe6d1798d034caf3b65aa504baf4681044573c Mon Sep 17 00:00:00 2001 From: Hiranmoy Das Chowdhury Date: Thu, 21 Nov 2024 17:44:53 +0600 Subject: [PATCH 06/29] restart Signed-off-by: Hiranmoy Das Chowdhury --- docs/guides/pgbouncer/restart/_index.md | 10 ++ docs/guides/pgbouncer/restart/restart.md | 189 +++++++++++++++++++++++ 2 files changed, 199 insertions(+) create mode 100644 docs/guides/pgbouncer/restart/_index.md create mode 100644 docs/guides/pgbouncer/restart/restart.md diff --git a/docs/guides/pgbouncer/restart/_index.md b/docs/guides/pgbouncer/restart/_index.md new file mode 100644 index 000000000..9b528e5b8 --- /dev/null +++ b/docs/guides/pgbouncer/restart/_index.md @@ -0,0 +1,10 @@ +--- +title: Restart PgBouncer +menu: + docs_{{ .version }}: + identifier: pb-restart + name: Restart + parent: pb-pgbouncer-guides + weight: 50 +menu_name: docs_{{ .version }} +--- diff --git a/docs/guides/pgbouncer/restart/restart.md b/docs/guides/pgbouncer/restart/restart.md new file mode 100644 index 000000000..688411ccd --- /dev/null +++ b/docs/guides/pgbouncer/restart/restart.md @@ -0,0 +1,189 @@ +--- +title: Restart PgBouncer +menu: + docs_{{ .version }}: + identifier: pb-restart-details + name: Restart PgBouncer + parent: pb-restart + weight: 10 +menu_name: docs_{{ .version }} +section_menu_id: guides +--- + +> New to KubeDB? Please start [here](/docs/README.md). + +# Restart PgBouncer + +KubeDB supports restarting the PgBouncer via a PgBouncerOpsRequest. Restarting is useful if some pods are got stuck in some phase, or they are not working correctly. This tutorial will show you how to use that. + +## Before You Begin + +- At first, you need to have a Kubernetes cluster, and the kubectl command-line tool must be configured to communicate with your cluster. If you do not already have a cluster, you can create one by using [kind](https://kind.sigs.k8s.io/docs/user/quick-start/). + +- Now, install KubeDB cli on your workstation and KubeDB operator in your cluster following the steps [here](/docs/setup/README.md). + +- To keep things isolated, this tutorial uses a separate namespace called `demo` throughout this tutorial. + +```bash + $ kubectl create ns demo + namespace/demo created + ``` + +> Note: YAML files used in this tutorial are stored in [docs/examples/pgbouncer](https://github.com/kubedb/docs/tree/{{< param "info.version" >}}/docs/examples/pgbouncer) folder in GitHub repository [kubedb/docs](https://github.com/kubedb/docs). + +## Prepare Postgres +Prepare a KubeDB Postgres cluster using this [tutorial](/docs/guides/postgres/clustering/streaming_replication.md), or you can use any externally managed postgres but in that case you need to create an [appbinding](/docs/guides/pgbouncer/concepts/appbinding.md) yourself. In this tutorial we will use 3 node Postgres cluster named `ha-postgres`. + +## Deploy PgBouncer + +In this section, we are going to deploy a PgBouncer using KubeDB. + +```yaml +apiVersion: kubedb.com/v1 +kind: PgBouncer +metadata: + name: pgbouncer + namespace: demo +spec: + replicas: 1 + version: "1.18.0" + database: + syncUsers: true + databaseName: "postgres" + databaseRef: + name: "ha-postgres" + namespace: demo + connectionPool: + poolMode: session + port: 5432 + reservePoolSize: 5 + maxClientConnections: 87 + defaultPoolSize: 2 + minPoolSize: 1 + authType: md5 + deletionPolicy: WipeOut +``` + +Let's create the `PgBouncer` CR we have shown above, + +```bash +$ kubectl create -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/pgbouncer/restart/pgbouncer.yaml +pgbouncer.kubedb.com/pgbouncer created +``` + +## Apply Restart opsRequest + +```yaml +apiVersion: ops.kubedb.com/v1alpha1 +kind: PgBouncerOpsRequest +metadata: + name: restart-pgbouncer + namespace: demo +spec: + type: Restart + databaseRef: + name: pgbouncer + timeout: 3m + apply: Always +``` + +- `spec.type` specifies the Type of the ops Request +- `spec.databaseRef` holds the name of the PgBouncer. The pgbouncer should be available in the same namespace as the opsRequest +- The meaning of `spec.timeout` & `spec.apply` fields will be found [here](/docs/guides/pgbouncer/concepts/opsrequest.md#spectimeout) + +Let's create the `PgBouncerOpsRequest` CR we have shown above, + +```bash +$ kubectl create -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/pgbouncer/restart/ops.yaml +pgbounceropsrequest.ops.kubedb.com/restart-pgbouncer created +``` + +Now the Ops-manager operator will restart the pods one by one. + +```shell +$ kubectl get pbops -n demo +NAME TYPE STATUS AGE +restart-pgbouncer Restart Successful 79s + +$ kubectl get pbops -n demo -oyaml restart-pgbouncer +apiVersion: ops.kubedb.com/v1alpha1 +kind: PgBouncerOpsRequest +metadata: + annotations: + kubectl.kubernetes.io/last-applied-configuration: | + {"apiVersion":"ops.kubedb.com/v1alpha1","kind":"PgBouncerOpsRequest","metadata":{"annotations":{},"name":"restart-pgbouncer","namespace":"demo"},"spec":{"apply":"Always","databaseRef":{"name":"pgbouncer"},"timeout":"3m","type":"Restart"}} + creationTimestamp: "2024-07-18T06:49:50Z" + generation: 1 + name: restart-pgbouncer + namespace: demo + resourceVersion: "94394" + uid: 8d3387fc-0c21-4e14-8bed-857a7cdf5423 +spec: + apply: Always + databaseRef: + name: pgbouncer + timeout: 3m + type: Restart +status: + conditions: + - lastTransitionTime: "2024-07-18T06:49:50Z" + message: PgBouncer ops-request has started to restart pgbouncer nodes + observedGeneration: 1 + reason: Restart + status: "True" + type: Restart + - lastTransitionTime: "2024-07-18T06:49:53Z" + message: Successfully paused database + observedGeneration: 1 + reason: DatabasePauseSucceeded + status: "True" + type: DatabasePauseSucceeded + - lastTransitionTime: "2024-07-18T06:50:38Z" + message: Successfully Restarted PgBouncer nodes + observedGeneration: 1 + reason: RestartNodes + status: "True" + type: RestartNodes + - lastTransitionTime: "2024-07-18T06:49:58Z" + message: get pod; ConditionStatus:True; PodName:pgbouncer-0 + observedGeneration: 1 + status: "True" + type: GetPod--pgbouncer-0 + - lastTransitionTime: "2024-07-18T06:49:58Z" + message: evict pod; ConditionStatus:True; PodName:pgbouncer-0 + observedGeneration: 1 + status: "True" + type: EvictPod--pgbouncer-0 + - lastTransitionTime: "2024-07-18T06:50:33Z" + message: check pod running; ConditionStatus:True; PodName:pgbouncer-0 + observedGeneration: 1 + status: "True" + type: CheckPodRunning--pgbouncer-0 + - lastTransitionTime: "2024-07-18T06:50:38Z" + message: Controller has successfully restart the PgBouncer replicas + observedGeneration: 1 + reason: Successful + status: "True" + type: Successful + observedGeneration: 1 + phase: Successful +``` + + +## Cleaning up + +To clean up the Kubernetes resources created by this tutorial, run: + +```bash +kubectl delete pgbounceropsrequest -n demo restart-pgbouncer +kubectl delete pgbouncer -n demo pgbouncer +kubectl delete ns demo +``` + +## Next Steps + +- Detail concepts of [PgBouncer object](/docs/guides/pgbouncer/concepts/pgbouncer.md). +- Monitor your PgBouncer database with KubeDB using [out-of-the-box Prometheus operator](/docs/guides/pgbouncer/monitoring/using-prometheus-operator.md). +- Monitor your PgBouncer database with KubeDB using [out-of-the-box builtin-Prometheus](/docs/guides/pgbouncer/monitoring/using-builtin-prometheus.md). +- Detail concepts of [PgBouncer object](/docs/guides/pgbouncer/concepts/pgbouncer.md). +- Want to hack on KubeDB? Check our [contribution guidelines](/docs/CONTRIBUTING.md). From f9a201cd70c34fc056fb5c05d724833e18d69d38 Mon Sep 17 00:00:00 2001 From: Hiranmoy Das Chowdhury Date: Fri, 22 Nov 2024 18:20:07 +0600 Subject: [PATCH 07/29] monitoring Signed-off-by: Hiranmoy Das Chowdhury --- docs/guides/pgbouncer/monitoring/_index.md | 2 +- docs/guides/pgbouncer/monitoring/overview.md | 106 ----- .../monitoring/setup-grafana-dashboard.md | 117 ------ .../monitoring/using-builtin-prometheus.md | 367 ------------------ .../monitoring/using-prometheus-operator.md | 286 -------------- 5 files changed, 1 insertion(+), 877 deletions(-) delete mode 100644 docs/guides/pgbouncer/monitoring/overview.md delete mode 100644 docs/guides/pgbouncer/monitoring/setup-grafana-dashboard.md delete mode 100644 docs/guides/pgbouncer/monitoring/using-builtin-prometheus.md delete mode 100644 docs/guides/pgbouncer/monitoring/using-prometheus-operator.md diff --git a/docs/guides/pgbouncer/monitoring/_index.md b/docs/guides/pgbouncer/monitoring/_index.md index dfd3aeaa8..2f5d8efa5 100755 --- a/docs/guides/pgbouncer/monitoring/_index.md +++ b/docs/guides/pgbouncer/monitoring/_index.md @@ -5,6 +5,6 @@ menu: identifier: pb-monitoring-pgbouncer name: Monitoring parent: pb-pgbouncer-guides - weight: 50 + weight: 100 menu_name: docs_{{ .version }} --- diff --git a/docs/guides/pgbouncer/monitoring/overview.md b/docs/guides/pgbouncer/monitoring/overview.md deleted file mode 100644 index 6230ab5b4..000000000 --- a/docs/guides/pgbouncer/monitoring/overview.md +++ /dev/null @@ -1,106 +0,0 @@ ---- -title: PgBouncer Monitoring Overview -description: PgBouncer Monitoring Overview -menu: - docs_{{ .version }}: - identifier: pb-monitoring-overview - name: Overview - parent: pb-monitoring-pgbouncer - weight: 10 -menu_name: docs_{{ .version }} -section_menu_id: guides ---- - -> New to KubeDB? Please start [here](/docs/README.md). - -# Monitoring PgBouncer with KubeDB - -KubeDB has native support for monitoring via [Prometheus](https://prometheus.io/). You can use builtin [Prometheus](https://github.com/prometheus/prometheus) scraper or [Prometheus operator](https://github.com/prometheus-operator/prometheus-operator) to monitor KubeDB managed databases. This tutorial will show you how database monitoring works with KubeDB and how to configure Database crd to enable monitoring. - -## Overview - -KubeDB uses Prometheus [exporter](https://prometheus.io/docs/instrumenting/exporters/#databases) images to export Prometheus metrics for respective databases. Following diagram shows the logical flow of database monitoring with KubeDB. - -

-  Database Monitoring Flow -

- -When a user creates a database crd with `spec.monitor` section configured, KubeDB operator provisions the respective database and injects an exporter image as sidecar to the database pod. It also creates a dedicated stats service with name `{database-crd-name}-stats` for monitoring. Prometheus server can scrape metrics using this stats service. - -## Configure Monitoring - -In order to enable monitoring for a database, you have to configure `spec.monitor` section. KubeDB provides following options to configure `spec.monitor` section: - -| Field | Type | Uses | -| -------------------------------------------------- | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | -| `spec.monitor.agent` | `Required` | Type of the monitoring agent that will be used to monitor this database. It can be `prometheus.io/builtin` or `prometheus.io/operator`. | -| `spec.monitor.prometheus.exporter.port` | `Optional` | Port number where the exporter side car will serve metrics. | -| `spec.monitor.prometheus.exporter.args` | `Optional` | Arguments to pass to the exporter sidecar. | -| `spec.monitor.prometheus.exporter.env` | `Optional` | List of environment variables to set in the exporter sidecar container. | -| `spec.monitor.prometheus.exporter.resources` | `Optional` | Resources required by exporter sidecar container. | -| `spec.monitor.prometheus.exporter.securityContext` | `Optional` | Security options the exporter should run with. | -| `spec.monitor.prometheus.serviceMonitor.labels` | `Optional` | Labels for `ServiceMonitor` crd. | -| `spec.monitor.prometheus.serviceMonitor.interval` | `Optional` | Interval at which metrics should be scraped. | - -## Sample Configuration - -A sample YAML for Redis crd with `spec.monitor` section configured to enable monitoring with [Prometheus operator](https://github.com/prometheus-operator/prometheus-operator) is shown below. - -```yaml -apiVersion: kubedb.com/v1 -kind: Redis -metadata: - name: sample-redis - namespace: databases -spec: - version: 6.0.20 - deletionPolicy: WipeOut - configSecret: # configure Redis to use password for authentication - name: redis-config - storageType: Durable - storage: - storageClassName: default - accessModes: - - ReadWriteOnce - resources: - requests: - storage: 5Gi - monitor: - agent: prometheus.io/operator - prometheus: - serviceMonitor: - labels: - release: prometheus - exporter: - args: - - --redis.password=$(REDIS_PASSWORD) - env: - - name: REDIS_PASSWORD - valueFrom: - secretKeyRef: - name: _name_of_secret_with_redis_password - key: password # key with the password - resources: - requests: - memory: 512Mi - cpu: 200m - limits: - memory: 512Mi - cpu: 250m - securityContext: - runAsUser: 2000 - allowPrivilegeEscalation: false -``` - -Assume that above Redis is configured to use basic authentication. So, exporter image also need to provide password to collect metrics. We have provided it through `spec.monitor.args` field. - -Here, we have specified that we are going to monitor this server using Prometheus operator through `spec.monitor.agent: prometheus.io/operator`. KubeDB will create a `ServiceMonitor` crd in `monitoring` namespace and this `ServiceMonitor` will have `release: prometheus` label. - -## Next Steps - -- Learn how to monitor Elasticsearch database with KubeDB using [builtin-Prometheus](/docs/guides/elasticsearch/monitoring/using-builtin-prometheus.md) and using [Prometheus operator](/docs/guides/elasticsearch/monitoring/using-prometheus-operator.md). -- Learn how to monitor PostgreSQL database with KubeDB using [builtin-Prometheus](/docs/guides/postgres/monitoring/using-builtin-prometheus.md) and using [Prometheus operator](/docs/guides/postgres/monitoring/using-prometheus-operator.md). -- Learn how to monitor MySQL database with KubeDB using [builtin-Prometheus](/docs/guides/mysql/monitoring/builtin-prometheus/index.md) and using [Prometheus operator](/docs/guides/mysql/monitoring/prometheus-operator/index.md). -- Learn how to monitor MongoDB database with KubeDB using [builtin-Prometheus](/docs/guides/mongodb/monitoring/using-builtin-prometheus.md) and using [Prometheus operator](/docs/guides/mongodb/monitoring/using-prometheus-operator.md). -- Learn how to monitor Redis server with KubeDB using [builtin-Prometheus](/docs/guides/redis/monitoring/using-builtin-prometheus.md) and using [Prometheus operator](/docs/guides/redis/monitoring/using-prometheus-operator.md). -- Learn how to monitor Memcached server with KubeDB using [builtin-Prometheus](/docs/guides/memcached/monitoring/using-builtin-prometheus.md) and using [Prometheus operator](/docs/guides/memcached/monitoring/using-prometheus-operator.md). diff --git a/docs/guides/pgbouncer/monitoring/setup-grafana-dashboard.md b/docs/guides/pgbouncer/monitoring/setup-grafana-dashboard.md deleted file mode 100644 index 911ba4b22..000000000 --- a/docs/guides/pgbouncer/monitoring/setup-grafana-dashboard.md +++ /dev/null @@ -1,117 +0,0 @@ ---- -title: Monitor PgBouncer using Prometheus Operator -menu: - docs_{{ .version }}: - identifier: pb-setup-grafana-dashboard-monitoring - name: Setup Grafana Dashboard - parent: pb-monitoring-pgbouncer - weight: 25 -menu_name: docs_{{ .version }} -section_menu_id: guides ---- - -> New to KubeDB? Please start [here](/docs/README.md). - -# Visualize PgBouncer Using Grafana Dashboard - -[Grafana](https://github.com/grafana/grafana) is an open source, feature rich metrics dashboard and graph editor for Graphite, Elasticsearch, OpenTSDB, Prometheus and InfluxDB. PgBouncer comes with a Grafana dashboard designed to monitor real-time updates of PgBouncer servers using Prometheus metrics. - -This tutorial will show you how to import our dashboard on Grafana to monitor PgBouncer deployed with KubeDB. - -## Before You Begin - -- At first, you need to have a Kubernetes cluster, and the kubectl command-line tool must be configured to communicate with your cluster. If you do not already have a cluster, you can create one by using [kind](https://kind.sigs.k8s.io/docs/user/quick-start/). - -- To learn how Prometheus monitoring works with KubeDB in general, please visit [here](/docs/guides/pgbouncer/monitoring/overview.md). - -- You need to have monitoring enabled using either [Builtin Prometheus](/docs/guides/pgbouncer/monitoring/using-builtin-prometheus.md) or [Prometheus operator](/docs/guides/pgbouncer/monitoring/using-builtin-prometheus.md). - -- To keep everything isolated, we are going to use a separate namespace called `monitoring` to deploy respective monitoring resources. We are going to deploy database in `demo` namespace. - - ```bash - $ kubectl create ns monitoring - namespace/monitoring created - - $ kubectl create ns demo - namespace/demo created - ``` - -> Note: YAML files used in this tutorial are stored in [docs/examples/pgbouncer](https://github.com/kubedb/docs/tree/{{< param "info.version" >}}/docs/examples/pgbouncer) folder in GitHub repository [kubedb/docs](https://github.com/kubedb/docs). - -## Deploy Grafana - -After you have made sure that you have a PgBouncer server running with Monitoring enabled, you're ready to deploy your very own Grafana server. If you still have not deployed PgBouncer server with monitoring enabled, then do so using [Builtin Prometheus](/docs/guides/pgbouncer/monitoring/using-builtin-prometheus.md) or [Prometheus operator](/docs/guides/pgbouncer/monitoring/using-builtin-prometheus.md). - -However, if you already have a Grafana server running in your cluster, feel free to skip this part. Otherwise, create one using: - -```bash -$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/pgbouncer/monitoring/grafana.yaml -deployment.apps/grafana created -``` - -Let's get the name of the pod created by this deployment: - -```bash -$ kubectl get pod -n monitoring -l "app=grafana" - -NAME READY STATUS RESTARTS AGE -grafana-7cbd6b6f87-w9dkh 1/1 Running 0 57s -``` - -## View Dashboard - -Now, we have to expose the Grafana pod so that we can access it from a browser. - -```bash -$ kubectl port-forward -n monitoring grafana-7cbd6b6f87-w9dkh 3000 -Forwarding from 127.0.0.1:3000 -> 3000 -Forwarding from [::1]:3000 -> 3000 -``` - -Grafana should now be available on [localhost](http://localhost:3000/). Use default credentials `(username: admin, password: admin)` to login to Grafana Dashboard. - -## Add Data Source - -First, we need to know the name of the service that exposes our prometheus server pods. In this tutorial, we have used a service named `prometheus-operated` that exposes our prometheus metrics on port 9090. - -```bash -$ kubectl get service -n monitoring -NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE -prometheus-operated ClusterIP 10.111.246.229 9090/TCP 38m -``` - -We will use this service to point Grafana to our desired data source. - -From Home Dashboard, go to [Configuration > Data Sources](http://localhost:3000/datasources), and select `Add data source`. Select `Prometheus` as the `data source type`. - -In the following screen, add `http://prometheus-operated.monitoring.svc:9090` as the data source `URL`, give it a name `PGBOUNCER_PROMETHEUS`, and press the `Save and Test` button. You should get a message confirming that the `Data source is working`. - -

-  Data Target -

- -## Import Dashboard - -Now, go to [http://localhost:3000/dashboard/import](http://localhost:3000/dashboard/import) to import our PgBouncer Dashboard. Put `10945` as the grafana dashboard id. Select `PGBOUNCER_PROMETHEUS` as the data source, and press `import`. You will now be directed to your PgBouncer dashboard. - -

-  Data Target -

- -## Cleaning up - -To cleanup the Kubernetes resources created by this tutorial, run the following commands - -```bash -# cleanup prometheus resources -kubectl delete -n monitoring deployment grafana - -# delete namespace -kubectl delete ns monitoring -``` - -## Next Steps - -- Monitor your PgBouncer with KubeDB using [built-in Prometheus](/docs/guides/pgbouncer/monitoring/using-builtin-prometheus.md). -- Monitor your PgBouncer with KubeDB using [Prometheus operator](/docs/guides/pgbouncer/monitoring/using-prometheus-operator.md). -- Want to hack on KubeDB? Check our [contribution guidelines](/docs/CONTRIBUTING.md). diff --git a/docs/guides/pgbouncer/monitoring/using-builtin-prometheus.md b/docs/guides/pgbouncer/monitoring/using-builtin-prometheus.md deleted file mode 100644 index f747fc248..000000000 --- a/docs/guides/pgbouncer/monitoring/using-builtin-prometheus.md +++ /dev/null @@ -1,367 +0,0 @@ ---- -title: Monitor PgBouncer using Builtin Prometheus Discovery -menu: - docs_{{ .version }}: - identifier: pb-using-builtin-prometheus-monitoring - name: Builtin Prometheus - parent: pb-monitoring-pgbouncer - weight: 20 -menu_name: docs_{{ .version }} -section_menu_id: guides ---- - -> New to KubeDB? Please start [here](/docs/README.md). - -# Monitoring PgBouncer with builtin Prometheus - -This tutorial will show you how to monitor PgBouncer using builtin [Prometheus](https://github.com/prometheus/prometheus) scraper. - -## Before You Begin - -- At first, you need to have a Kubernetes cluster, and the kubectl command-line tool must be configured to communicate with your cluster. If you do not already have a cluster, you can create one by using [kind](https://kind.sigs.k8s.io/docs/user/quick-start/). - -- Install KubeDB operator in your cluster following the steps [here](/docs/setup/README.md). - -- If you are not familiar with how to configure Prometheus to scrape metrics from various Kubernetes resources, please read the tutorial from [here](https://github.com/appscode/third-party-tools/tree/master/monitoring/prometheus/builtin). - -- To learn how Prometheus monitoring works with KubeDB in general, please visit [here](/docs/guides/pgbouncer/monitoring/overview.md). - -- To keep Prometheus resources isolated, we are going to use a separate namespace called `monitoring` to deploy respective monitoring resources. We are going to deploy database in `demo` namespace. - - ```bash - $ kubectl create ns monitoring - namespace/monitoring created - - $ kubectl create ns demo - namespace/demo created - ``` - -> Note: YAML files used in this tutorial are stored in [docs/examples/pgbouncer](https://github.com/kubedb/docs/tree/{{< param "info.version" >}}/docs/examples/pgbouncer) folder in GitHub repository [kubedb/docs](https://github.com/kubedb/docs). - -## Deploy PgBouncer with Monitoring Enabled - -At first, we will need a PgBouncer with monitoring enabled. This PgBouncer needs to be connected to PostgreSQL database(s). You can get a PgBouncer setup with active connection(s) to PostgreSQL by following the [quickstart](/docs/guides/pgbouncer/quickstart/quickstart.md) guide. PgBouncer object in that guide didn't come with monitoring. So we are going to enable monitoring in it. Below is the PgBouncer object that contains built-in monitoring: - -```yaml -apiVersion: kubedb.com/v1 -kind: PgBouncer -metadata: - name: pgbouncer-server - namespace: demo -spec: - version: "1.17.0" - replicas: 1 - database: - syncUsers: true - databaseName: "postgres" - databaseRef: - name: "quick-postgres" - namespace: demo - connectionPool: - maxClientConnections: 20 - reservePoolSize: 5 - monitor: - agent: prometheus.io/builtin -``` - -Here, - -- `spec.monitor.agent: prometheus.io/builtin` specifies that we are going to monitor this server using builtin Prometheus scraper. - -Let's patch the existing PgBouncer with the crd we have shown above. - -```bash -$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/pgbouncer/monitoring/builtin-prom-pgbouncer.yaml -pgbouncer.kubedb.com/pgbouncer-server configured -``` - -PgBouncer should still be in `Running` state. - -```bash -$ kubectl get pb -n demo pgbouncer-server -NAME VERSION STATUS AGE -pgbouncer-server 1.17.0 Running 13s -``` - -KubeDB will create a separate stats service with name `{PgBouncer crd name}-stats` for monitoring purpose. - -```bash -$ kubectl get svc -n demo --selector="app.kubernetes.io/instance=pgbouncer-server" -NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE -pgbouncer-server ClusterIP 10.108.152.208 5432/TCP 16m -pgbouncer-server-stats ClusterIP 10.111.194.83 56790/TCP 16m -``` - -Here, `pgbouncer-server-stats` service has been created for monitoring purpose. Let's describe the service. - -```bash -$ kubectl describe svc -n demo pgbouncer-server-stats -Name: pgbouncer-server-stats -Namespace: demo -Labels: app.kubernetes.io/name=pgbouncers.kubedb.com - app.kubernetes.io/instance=pgbouncer-server - kubedb.com/role=stats -Annotations: monitoring.appscode.com/agent: prometheus.io/builtin - prometheus.io/path: /metrics - prometheus.io/port: 56790 - prometheus.io/scrape: true -Selector: app.kubernetes.io/name=pgbouncers.kubedb.com,app.kubernetes.io/instance=pgbouncer-server -Type: ClusterIP -IP: 10.110.56.149 -Port: prom-http 56790/TCP -TargetPort: prom-http/TCP -Endpoints: 172.17.0.7:56790 -Session Affinity: None -Events: -``` - -You can see that the service contains following annotations. - -```bash -prometheus.io/path: /metrics -prometheus.io/port: 56790 -prometheus.io/scrape: true -``` - -The Prometheus server will discover the service endpoint using these specifications and will scrape metrics from the exporter. - -## Configure Prometheus Server - -Now, we have to configure a Prometheus scraping job to scrape the metrics using this service. We are going to configure scraping job similar to this [kubernetes-service-endpoints](https://github.com/appscode/third-party-tools/tree/master/monitoring/prometheus/builtin#kubernetes-service-endpoints) job that scrapes metrics from endpoints of a service. - -Let's configure a Prometheus scraping job to collect metrics from this service. - -```yaml -- job_name: 'kubedb-databases' - honor_labels: true - scheme: http - kubernetes_sd_configs: - - role: endpoints - # by default Prometheus server select all Kubernetes services as possible target. - # relabel_config is used to filter only desired endpoints - relabel_configs: - # keep only those services that has "prometheus.io/scrape","prometheus.io/path" and "prometheus.io/port" anootations - - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scrape, __meta_kubernetes_service_annotation_prometheus_io_port] - separator: ; - regex: true;(.*) - action: keep - # currently KubeDB supported databases uses only "http" scheme to export metrics. so, drop any service that uses "https" scheme. - - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scheme] - action: drop - regex: https - # only keep the stats services created by KubeDB for monitoring purpose which has "-stats" suffix - - source_labels: [__meta_kubernetes_service_name] - separator: ; - regex: (.*-stats) - action: keep - # service created by KubeDB will have "app.kubernetes.io/name" and "app.kubernetes.io/instance" annotations. keep only those services that have these annotations. - - source_labels: [__meta_kubernetes_service_label_app_kubernetes_io_name] - separator: ; - regex: (.*) - action: keep - # read the metric path from "prometheus.io/path: " annotation - - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_path] - action: replace - target_label: __metrics_path__ - regex: (.+) - # read the port from "prometheus.io/port: " annotation and update scraping address accordingly - - source_labels: [__address__, __meta_kubernetes_service_annotation_prometheus_io_port] - action: replace - target_label: __address__ - regex: ([^:]+)(?::\d+)?;(\d+) - replacement: $1:$2 - # add service namespace as label to the scraped metrics - - source_labels: [__meta_kubernetes_namespace] - separator: ; - regex: (.*) - target_label: namespace - replacement: $1 - action: replace - # add service name as a label to the scraped metrics - - source_labels: [__meta_kubernetes_service_name] - separator: ; - regex: (.*) - target_label: service - replacement: $1 - action: replace - # add stats service's labels to the scraped metrics - - action: labelmap - regex: __meta_kubernetes_service_label_(.+) -``` - -### Configure Existing Prometheus Server - -If you already have a Prometheus server running, you have to add above scraping job in the `ConfigMap` used to configure the Prometheus server. Then, you have to restart it for the updated configuration to take effect. - -> If you don't use a persistent volume for Prometheus storage, you will lose your previously scraped data on restart. - -### Deploy New Prometheus Server - -If you don't have any existing Prometheus server running, you have to deploy one. In this section, we are going to deploy a Prometheus server in `monitoring` namespace to collect metrics using this stats service. - -**Create ConfigMap:** - -At first, create a ConfigMap with the scraping configuration. Bellow, the YAML of ConfigMap that we are going to create in this tutorial. - -```yaml -apiVersion: v1 -kind: ConfigMap -metadata: - name: prometheus-config - labels: - app: prometheus-demo - namespace: monitoring -data: - prometheus.yml: |- - global: - scrape_interval: 5s - evaluation_interval: 5s - scrape_configs: - - job_name: 'kubedb-databases' - honor_labels: true - scheme: http - kubernetes_sd_configs: - - role: endpoints - # by default Prometheus server select all Kubernetes services as possible target. - # relabel_config is used to filter only desired endpoints - relabel_configs: - # keep only those services that has "prometheus.io/scrape","prometheus.io/path" and "prometheus.io/port" anootations - - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scrape, __meta_kubernetes_service_annotation_prometheus_io_port] - separator: ; - regex: true;(.*) - action: keep - # currently KubeDB supported databases uses only "http" scheme to export metrics. so, drop any service that uses "https" scheme. - - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scheme] - action: drop - regex: https - # only keep the stats services created by KubeDB for monitoring purpose which has "-stats" suffix - - source_labels: [__meta_kubernetes_service_name] - separator: ; - regex: (.*-stats) - action: keep - # service created by KubeDB will have "app.kubernetes.io/name" and "app.kubernetes.io/instance" annotations. keep only those services that have these annotations. - - source_labels: [__meta_kubernetes_service_label_app_kubernetes_io_name] - separator: ; - regex: (.*) - action: keep - # read the metric path from "prometheus.io/path: " annotation - - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_path] - action: replace - target_label: __metrics_path__ - regex: (.+) - # read the port from "prometheus.io/port: " annotation and update scraping address accordingly - - source_labels: [__address__, __meta_kubernetes_service_annotation_prometheus_io_port] - action: replace - target_label: __address__ - regex: ([^:]+)(?::\d+)?;(\d+) - replacement: $1:$2 - # add service namespace as label to the scraped metrics - - source_labels: [__meta_kubernetes_namespace] - separator: ; - regex: (.*) - target_label: namespace - replacement: $1 - action: replace - # add service name as a label to the scraped metrics - - source_labels: [__meta_kubernetes_service_name] - separator: ; - regex: (.*) - target_label: service - replacement: $1 - action: replace - # add stats service's labels to the scraped metrics - - action: labelmap - regex: __meta_kubernetes_service_label_(.+) -``` - -Let's create above `ConfigMap`, - -```bash -$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/monitoring/builtin-prometheus/prom-config.yaml -configmap/prometheus-config created -``` - -**Create RBAC:** - -If you are using an RBAC enabled cluster, you have to give necessary RBAC permissions for Prometheus. Let's create necessary RBAC stuffs for Prometheus, - -```bash -$ kubectl apply -f https://github.com/appscode/third-party-tools/raw/master/monitoring/prometheus/builtin/artifacts/rbac.yaml -clusterrole.rbac.authorization.k8s.io/prometheus created -serviceaccount/prometheus created -clusterrolebinding.rbac.authorization.k8s.io/prometheus created -``` - ->YAML for the RBAC resources created above can be found [here](https://github.com/appscode/third-party-tools/blob/master/monitoring/prometheus/builtin/artifacts/rbac.yaml). - -**Deploy Prometheus:** - -Now, we are ready to deploy Prometheus server. We are going to use following [deployment](https://github.com/appscode/third-party-tools/blob/master/monitoring/prometheus/builtin/artifacts/deployment.yaml) to deploy Prometheus server. - -Let's deploy the Prometheus server. - -```bash -$ kubectl apply -f https://github.com/appscode/third-party-tools/raw/master/monitoring/prometheus/builtin/artifacts/deployment.yaml -deployment.apps/prometheus created -``` - -**Prometheus Service:** - -We will use a service for the Prometheus server. We can use this to look up metrics from within the cluster as well as outside of the cluster. - -```bash -$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/pgbouncer/monitoring/builtin-prom-service.yaml -service/prometheus-operated created -``` - -### Verify Monitoring Metrics - -Prometheus server is listening to port `9090`. We are going to use [port forwarding](https://kubernetes.io/docs/tasks/access-application-cluster/port-forward-access-application-cluster/) to access Prometheus dashboard. - -At first, let's check if the Prometheus pod is in `Running` state. - -```bash -kubectl get pod -n monitoring -l=app=prometheus -NAME READY STATUS RESTARTS AGE -prometheus-789c9695fc-7rjzf 1/1 Running 0 27s -``` - -Now, run following command on a separate terminal to forward 9090 port of `prometheus-8568c86d86-95zhn` pod, - -```bash -$ kubectl port-forward -n monitoring svc/prometheus-operated 9090 -Forwarding from 127.0.0.1:9090 -> 9090 -Forwarding from [::1]:9090 -> 9090 -``` - -Now, we can access the dashboard at `localhost:9090`. Open [http://localhost:9090/targets](http://localhost:9090/targets) in your browser. You should see the endpoint of `pgbouncer-server-stats` service as one of the targets. - -

-  Prometheus Target -

- -Check the labels which confirm that the metrics are coming from `pgbouncer-server` through stats service `pgbouncer-server-stats`. - -Now, you can view the collected metrics and create a graph from homepage of this Prometheus dashboard. You can also use this Prometheus server as data source for [Grafana](https://grafana.com/) and create beautiful dashboard with collected metrics. - -## Cleaning up - -To cleanup the Kubernetes resources created by this tutorial, run following commands - -```bash -$ kubectl delete -n demo pb/pgbouncer-server - -$ kubectl delete -n monitoring deployment.apps/prometheus - -$ kubectl delete -n monitoring clusterrole.rbac.authorization.k8s.io/prometheus -$ kubectl delete -n monitoring serviceaccount/prometheus -$ kubectl delete -n monitoring clusterrolebinding.rbac.authorization.k8s.io/prometheus - -$ kubectl delete ns demo -$ kubectl delete ns monitoring -``` - -## Next Steps -- Monitor your PgBouncer with KubeDB using [`out-of-the-box` Prometheus operator](/docs/guides/pgbouncer/monitoring/using-prometheus-operator.md). -- Use [private Docker registry](/docs/guides/pgbouncer/private-registry/using-private-registry.md) to deploy PgBouncer with KubeDB. -- Want to hack on KubeDB? Check our [contribution guidelines](/docs/CONTRIBUTING.md). diff --git a/docs/guides/pgbouncer/monitoring/using-prometheus-operator.md b/docs/guides/pgbouncer/monitoring/using-prometheus-operator.md deleted file mode 100644 index bc8d5f8fd..000000000 --- a/docs/guides/pgbouncer/monitoring/using-prometheus-operator.md +++ /dev/null @@ -1,286 +0,0 @@ ---- -title: Monitor PgBouncer using Prometheus Operator -menu: - docs_{{ .version }}: - identifier: pb-using-prometheus-operator-monitoring - name: Prometheus Operator - parent: pb-monitoring-pgbouncer - weight: 15 -menu_name: docs_{{ .version }} -section_menu_id: guides ---- - -> New to KubeDB? Please start [here](/docs/README.md). - -# Monitoring PgBouncer using Prometheus operator - -[Prometheus operator](https://github.com/prometheus-operator/prometheus-operator) provides simple and Kubernetes native way to deploy and configure Prometheus server. This tutorial will show you how to use Prometheus operator to monitor PgBouncer deployed with KubeDB. - -## Before You Begin - -- At first, you need to have a Kubernetes cluster, and the kubectl command-line tool must be configured to communicate with your cluster. If you do not already have a cluster, you can create one by using [kind](https://kind.sigs.k8s.io/docs/user/quick-start/). - -- To learn how Prometheus monitoring works with KubeDB in general, please visit [here](/docs/guides/pgbouncer/monitoring/overview.md). - -- To keep Prometheus resources isolated, we are going to use a separate namespace called `monitoring` to deploy respective monitoring resources. We are going to deploy database in `demo` namespace. - - ```bash - $ kubectl create ns monitoring - namespace/monitoring created - ``` - -- We need a [Prometheus operator](https://github.com/prometheus-operator/prometheus-operator) instance running. If you don't already have a running instance, deploy one following the docs from [here](https://github.com/appscode/third-party-tools/blob/master/monitoring/prometheus/operator/README.md). - -- If you already don't have a Prometheus server running, deploy one following tutorial from [here](https://github.com/appscode/third-party-tools/blob/master/monitoring/prometheus/operator/README.md#deploy-prometheus-server). - -> Note: YAML files used in this tutorial are stored in [docs/examples/pgbouncer](https://github.com/kubedb/docs/tree/{{< param "info.version" >}}/docs/examples/pgbouncer) folder in GitHub repository [kubedb/docs](https://github.com/kubedb/docs). - -## Find out required labels for ServiceMonitor - -We need to know the labels used to select `ServiceMonitor` by a `Prometheus` crd. We are going to provide these labels in `spec.monitor.prometheus.labels` field of PgBouncer crd so that KubeDB creates `ServiceMonitor` object accordingly. - -As a prerequisite, we need to have Prometheus operator running, and a prometheus server created to monitor PgBouncer exporter. In this tutorial we are going to use a prometheus server named `promethus` in `monitoring` namespace. You can use the following to install `Prometheus operator`. - -```bash -$ kubectl apply -f https://raw.githubusercontent.com/appscode/third-party-tools/master/monitoring/prometheus/coreos-operator/artifacts/operator.yaml -``` - -Now, get a prometheus server up and running. - -```bash -$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/pgbouncer/monitoring/coreos-prom-server.yaml - -clusterrole.rbac.authorization.k8s.io/prometheus created -serviceaccount/prometheus created -clusterrolebinding.rbac.authorization.k8s.io/prometheus created -prometheus.monitoring.coreos.com/prometheus created -``` - -Now, let's find out the available Prometheus server in our cluster. - -```bash - -$ kubectl get prometheus --all-namespaces -NAMESPACE NAME AGE -default tufted-rodent-prometheus-o-prometheus 3h42m -monitoring prometheus 18m -``` - -Now, let's view the YAML of the available Prometheus server `prometheus` in `monitoring` namespace. - -```yaml -$ kubectl get prometheus -n monitoring prometheus -o yaml -apiVersion: monitoring.coreos.com/v1 -kind: Prometheus -metadata: - annotations: - kubectl.kubernetes.io/last-applied-configuration: | - {"apiVersion":"monitoring.coreos.com/v1","kind":"Prometheus","metadata":{"annotations":{},"labels":{"prometheus":"prometheus"},"name":"prometheus","namespace":"monitoring"},"spec":{"replicas":1,"resources":{"requests":{"memory":"400Mi"}},"serviceAccountName":"prometheus","serviceMonitorSelector":{"matchLabels":{"release":"prometheus"}}}} - creationTimestamp: "2019-09-19T09:32:12Z" - generation: 1 - labels: - prometheus: prometheus - name: prometheus - namespace: monitoring - resourceVersion: "38348" - selfLink: /apis/monitoring.coreos.com/v1/namespaces/monitoring/prometheuses/prometheus - uid: f9285974-3349-40e8-815a-8f50c3a8a4f5 -spec: - replicas: 1 - resources: - requests: - memory: 400Mi - serviceAccountName: prometheus - serviceMonitorSelector: - matchLabels: - release: prometheus -``` - -Notice the `spec.serviceMonitorSelector` section. Here, `release: prometheus` label is used to select `ServiceMonitor` crd. So, we are going to use this label in `spec.monitor.prometheus.labels` field of PgBouncer crd. - -## Deploy PgBouncer with Monitoring Enabled - -We will need a PgBouncer with monitoring enabled. This PgBouncer needs to be connected to PostgreSQL database(s). You can get a PgBouncer setup with active connection(s) to PostgreSQL by following the [quickstart](/docs/guides/pgbouncer/quickstart/quickstart.md) guide. PgBouncer object in that guide didn't come with monitoring. So we are going to enable monitoring in it. Below is the PgBouncer object that contains Prometheus operator based monitoring: - -```yaml -apiVersion: kubedb.com/v1 -kind: PgBouncer -metadata: - name: pgbouncer-server - namespace: demo -spec: - version: "1.17.0" - replicas: 1 - database: - syncUsers: true - databaseName: "postgres" - databaseRef: - name: "quick-postgres" - namespace: demo - connectionPool: - maxClientConnections: 20 - reservePoolSize: 5 - monitor: - agent: prometheus.io/operator - prometheus: - serviceMonitor: - labels: - release: prometheus - interval: 10s -``` - -Here, - -- `monitor.agent: prometheus.io/operator` indicates that we are going to monitor this server using Prometheus operator. -- `monitor.prometheus.namespace: monitoring` specifies that KubeDB should create `ServiceMonitor` in `monitoring` namespace. - -- `monitor.prometheus.labels` specifies that KubeDB should create `ServiceMonitor` with these labels. - -- `monitor.prometheus.interval` indicates that the Prometheus server should scrape metrics from this database with 10 seconds interval. - -Let's create the PgBouncer object that we have shown above, - -```bash -$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/pgbouncer/monitoring/coreos-prom-pgbouncer.yaml -pgbouncer.kubedb.com/pgbouncer-server configured -``` - -Now, wait for the database to go into `Running` state. - -```bash -$ kubectl get pb -n demo pgbouncer-server -NAME VERSION STATUS AGE -pgbouncer-server 1.17.0 Running 10s -``` - -KubeDB will create a separate stats service with name `{PgBouncer crd name}-stats` for monitoring purpose. - -```bash -$ kubectl get svc -n demo --selector="app.kubernetes.io/instance=pgbouncer-server" -NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE -pgbouncer-server ClusterIP 10.104.83.201 5432/TCP 52s -pgbouncer-server-stats ClusterIP 10.101.214.117 56790/TCP 50s -``` - -Here, `pgbouncer-server-stats` service has been created for monitoring purpose. - -Let's describe this stats service. - -```yaml -$ kubectl describe svc -n demo pgbouncer-server-stats -Name: pgbouncer-server-stats -Namespace: demo -Labels: app.kubernetes.io/name=pgbouncers.kubedb.com - app.kubernetes.io/instance=pgbouncer-server - kubedb.com/role=stats -Annotations: monitoring.appscode.com/agent:prometheus.io/operator -Selector: app.kubernetes.io/name=pgbouncers.kubedb.com,app.kubernetes.io/instance=pgbouncer-server -Type: ClusterIP -IP: 10.101.214.117 -Port: prom-http 56790/TCP -TargetPort: prom-http/TCP -Endpoints: 172.17.0.7:56790 -Session Affinity: None -``` - -Notice the `Labels` and `Port` fields. `ServiceMonitor` will use these information to target its endpoints. - -KubeDB will also create a `ServiceMonitor` crd in `monitoring` namespace that select the endpoints of `pgbouncer-server-stats` service. Verify that the `ServiceMonitor` crd has been created. - -```bash -$ kubectl get servicemonitor -n monitoring -NAME AGE -kubedb-demo-pgbouncer-server 3m4s -``` - -Let's verify that the `ServiceMonitor` has the label that we had specified in `spec.monitor` section of PgBouncer crd. - -```yaml -$ kubectl get servicemonitor -n monitoring kubedb-demo-pgbouncer-server -o yaml -apiVersion: monitoring.coreos.com/v1 -kind: ServiceMonitor -metadata: - creationTimestamp: "2019-09-19T10:03:24Z" - generation: 1 - labels: - release: prometheus - monitoring.appscode.com/service: pgbouncer-server-stats.demo - name: kubedb-demo-pgbouncer-server - namespace: monitoring - ownerReferences: - - apiVersion: v1 - blockOwnerDeletion: true - kind: Service - name: pgbouncer-server-stats - uid: 749bc2ed-e14c-4a9e-9688-9d319af2b902 - resourceVersion: "41639" - selfLink: /apis/monitoring.coreos.com/v1/namespaces/monitoring/servicemonitors/kubedb-demo-pgbouncer-server - uid: 4a68d942-a003-4b47-a8cb-f20e526e9748 -spec: - endpoints: - - honorLabels: true - interval: 5s - path: /metrics - port: prom-http - namespaceSelector: - matchNames: - - demo - selector: - matchLabels: - app.kubernetes.io/name: pgbouncers.kubedb.com - app.kubernetes.io/instance: pgbouncer-server - kubedb.com/role: stats -``` - -Notice that the `ServiceMonitor` has label `release: prometheus` that we had specified in PgBouncer crd. - -Also notice that the `ServiceMonitor` has selector which match the labels we have seen in the `pgbouncer-server-stats` service. It also, target the `prom-http` port that we have seen in the stats service. - -## Verify Monitoring Metrics - -At first, let's find out the respective Prometheus pod for `prometheus` Prometheus server. - -```bash -$ kubectl get pod -n monitoring -l=app=prometheus -NAME READY STATUS RESTARTS AGE -prometheus-prometheus-0 3/3 Running 1 35m -``` - -Prometheus server is listening to port `9090` of `prometheus-prometheus-0` pod. We are going to use [port forwarding](https://kubernetes.io/docs/tasks/access-application-cluster/port-forward-access-application-cluster/) to access Prometheus dashboard. - -Run following command on a separate terminal to forward the port 9090 of `prometheus-prometheus-0` pod, - -```bash -$ kubectl port-forward -n monitoring prometheus-prometheus-0 9090 -Forwarding from 127.0.0.1:9090 -> 9090 -Forwarding from [::1]:9090 -> 9090 -``` - -Now, we can access the dashboard at `localhost:9090`. Open [http://localhost:9090/targets](http://localhost:9090/targets) in your browser. You should see `prom-http` endpoint of `pgbouncer-server-stats` service as one of the targets. - -

-  Prometheus Target -

- -Check the `endpoint` and `service` labels which verify that the target is our expected database. Now, you can view the collected metrics and create a graph from homepage of this Prometheus dashboard. You can also use this Prometheus server as data source for [Grafana](https://grafana.com/) and create beautiful dashboard with collected metrics. - -## Cleaning up - -To cleanup the Kubernetes resources created by this tutorial, run the following commands - -```bash -# cleanup prometheus resources -kubectl delete -n monitoring prometheus prometheus -kubectl delete -n monitoring clusterrolebinding prometheus -kubectl delete -n monitoring clusterrole prometheus -kubectl delete -n monitoring serviceaccount prometheus -kubectl delete -n monitoring service prometheus-operated - -# delete namespace -kubectl delete ns monitoring -``` - -## Next Steps - -- Monitor your PgBouncer with KubeDB using [built-in Prometheus](/docs/guides/pgbouncer/monitoring/using-builtin-prometheus.md). -- Want to hack on KubeDB? Check our [contribution guidelines](/docs/CONTRIBUTING.md). From fe2f925b7ebdc42297789a810bd865d4e2548304 Mon Sep 17 00:00:00 2001 From: Hiranmoy Das Chowdhury Date: Mon, 25 Nov 2024 18:40:01 +0600 Subject: [PATCH 08/29] Signed-off-by: Hiranmoy Das Chowdhury --- docs/guides/pgbouncer/monitoring/overview.md | 92 +++++ .../monitoring/using-builtin-prometheus.md | 369 +++++++++++++++++ .../monitoring/using-prometheus-operator.md | 371 ++++++++++++++++++ 3 files changed, 832 insertions(+) create mode 100644 docs/guides/pgbouncer/monitoring/overview.md create mode 100644 docs/guides/pgbouncer/monitoring/using-builtin-prometheus.md create mode 100644 docs/guides/pgbouncer/monitoring/using-prometheus-operator.md diff --git a/docs/guides/pgbouncer/monitoring/overview.md b/docs/guides/pgbouncer/monitoring/overview.md new file mode 100644 index 000000000..4f6d5247f --- /dev/null +++ b/docs/guides/pgbouncer/monitoring/overview.md @@ -0,0 +1,92 @@ +--- +title: PgBouncer Monitoring Overview +description: PgBouncer Monitoring Overview +menu: + docs_{{ .version }}: + identifier: pb-monitoring-overview + name: Overview + parent: pb-monitoring-pgbouncer + weight: 10 +menu_name: docs_{{ .version }} +section_menu_id: guides +--- + +> New to KubeDB? Please start [here](/docs/README.md). + +# Monitoring PgBouncer with KubeDB + +KubeDB has native support for monitoring via [Prometheus](https://prometheus.io/). You can use builtin [Prometheus](https://github.com/prometheus/prometheus) scraper or [Prometheus operator](https://github.com/prometheus-operator/prometheus-operator) to monitor KubeDB managed databases. This tutorial will show you how database monitoring works with KubeDB and how to configure Database crd to enable monitoring. + +## Overview + +KubeDB uses Prometheus [exporter](https://prometheus.io/docs/instrumenting/exporters/#databases) images to export Prometheus metrics for respective databases. Following diagram shows the logical flow of database monitoring with KubeDB. + +

+  Database Monitoring Flow +

+ +When a user creates a database crd with `spec.monitor` section configured, KubeDB operator provisions the respective database and injects an exporter image as sidecar to the database pod. It also creates a dedicated stats service with name `{database-crd-name}-stats` for monitoring. Prometheus server can scrape metrics using this stats service. + +## Configure Monitoring + +In order to enable monitoring for a database, you have to configure `spec.monitor` section. KubeDB provides following options to configure `spec.monitor` section: + +| Field | Type | Uses | +| -------------------------------------------------- | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | +| `spec.monitor.agent` | `Required` | Type of the monitoring agent that will be used to monitor this database. It can be `prometheus.io/builtin` or `prometheus.io/operator`. | +| `spec.monitor.prometheus.exporter.port` | `Optional` | Port number where the exporter side car will serve metrics. | +| `spec.monitor.prometheus.exporter.args` | `Optional` | Arguments to pass to the exporter sidecar. | +| `spec.monitor.prometheus.exporter.env` | `Optional` | List of environment variables to set in the exporter sidecar container. | +| `spec.monitor.prometheus.exporter.resources` | `Optional` | Resources required by exporter sidecar container. | +| `spec.monitor.prometheus.exporter.securityContext` | `Optional` | Security options the exporter should run with. | +| `spec.monitor.prometheus.serviceMonitor.labels` | `Optional` | Labels for `ServiceMonitor` crd. | +| `spec.monitor.prometheus.serviceMonitor.interval` | `Optional` | Interval at which metrics should be scraped. | + +## Sample Configuration + +A sample YAML for PgBouncer crd with `spec.monitor` section configured to enable monitoring with [Prometheus operator](https://github.com/prometheus-operator/prometheus-operator) is shown below. + +```yaml +apiVersion: kubedb.com/v1 +kind: PgBouncer +metadata: + name: pb + namespace: demo +spec: + replicas: 2 + version: "1.18.0" + database: + syncUsers: true + databaseName: "postgres" + databaseRef: + name: "pg" + namespace: demo + connectionPool: + poolMode: session + port: 5432 + monitor: + agent: prometheus.io/operator + prometheus: + serviceMonitor: + labels: + release: prometheus + exporter: + resources: + requests: + memory: 512Mi + cpu: 200m + limits: + memory: 512Mi + cpu: 250m + securityContext: + runAsUser: 70 + allowPrivilegeEscalation: false +``` + +Here, we have specified that we are going to monitor this server using Prometheus operator through `spec.monitor.agent: prometheus.io/operator`. KubeDB will create a `ServiceMonitor` crd in databases namespace and this `ServiceMonitor` will have `release: prometheus` label. + +## Next Steps + +- Learn how to monitor PgBouncer database with KubeDB using [builtin-Prometheus](/docs/guides/pgbouncer/monitoring/using-builtin-prometheus.md) +- Learn how to monitor PgBouncer database with KubeDB using [Prometheus operator](/docs/guides/pgbouncer/monitoring/using-prometheus-operator.md). + diff --git a/docs/guides/pgbouncer/monitoring/using-builtin-prometheus.md b/docs/guides/pgbouncer/monitoring/using-builtin-prometheus.md new file mode 100644 index 000000000..606e782eb --- /dev/null +++ b/docs/guides/pgbouncer/monitoring/using-builtin-prometheus.md @@ -0,0 +1,369 @@ +--- +title: Monitor PgBouncer using Builtin Prometheus Discovery +menu: + docs_{{ .version }}: + identifier: pb-using-builtin-prometheus-monitoring + name: Builtin Prometheus + parent: pb-monitoring-pgbouncer + weight: 20 +menu_name: docs_{{ .version }} +section_menu_id: guides +--- + +> New to KubeDB? Please start [here](/docs/README.md). + +# Monitoring PgBouncer with builtin Prometheus + +This tutorial will show you how to monitor PgBouncer database using builtin [Prometheus](https://github.com/prometheus/prometheus) scraper. + +## Before You Begin + +- At first, you need to have a Kubernetes cluster, and the kubectl command-line tool must be configured to communicate with your cluster. If you do not already have a cluster, you can create one by using [kind](https://kind.sigs.k8s.io/docs/user/quick-start/). + +- Install KubeDB operator in your cluster following the steps [here](/docs/setup/README.md). + +- If you are not familiar with how to configure Prometheus to scrape metrics from various Kubernetes resources, please read the tutorial from [here](https://github.com/appscode/third-party-tools/tree/master/monitoring/prometheus/builtin). + +- To learn how Prometheus monitoring works with KubeDB in general, please visit [here](/docs/guides/pgbouncer/monitoring/overview.md). + +- To keep Prometheus resources isolated, we are going to use a separate namespace called `monitoring` to deploy respective monitoring resources. We are going to deploy database in `demo` namespace. + + ```bash + $ kubectl create ns monitoring + namespace/monitoring created + + $ kubectl create ns demo + namespace/demo created + ``` + +> Note: YAML files used in this tutorial are stored in [docs/examples/pgbouncer](https://github.com/kubedb/docs/tree/{{< param "info.version" >}}/docs/examples/pgbouncer) folder in GitHub repository [kubedb/docs](https://github.com/kubedb/docs). + +## Prepare Postgres +Prepare a KubeDB Postgres cluster using this [tutorial](/docs/guides/postgres/clustering/streaming_replication.md), or you can use any externally managed postgres but in that case you need to create an [appbinding](/docs/guides/pgbouncer/concepts/appbinding.md) yourself. In this tutorial we will use 3 node Postgres cluster named `ha-postgres`. + +## Deploy PgBouncer with Monitoring Enabled + +At first, let's deploy a PgBouncer with monitoring enabled. Below is the PgBouncer object that we are going to create. + +```yaml +apiVersion: kubedb.com/v1 +kind: PgBouncer +metadata: + name: builtin-prom-pb + namespace: demo +spec: + replicas: 1 + version: "1.18.0" + database: + syncUsers: true + databaseName: "postgres" + databaseRef: + name: "ha-postgres" + namespace: demo + connectionPool: + poolMode: session + port: 5432 + monitor: + agent: prometheus.io/builtin +``` + +Here, + +- `spec.monitor.agent: prometheus.io/builtin` specifies that we are going to monitor this server using builtin Prometheus scraper. + +Let's create the PgBouncer crd we have shown above. + +```bash +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/pgbouncer/monitoring/builtin-prom-pb.yaml +pgbouncer.kubedb.com/builtin-prom-pb created +``` + +Now, wait for the database to go into `Running` state. + +```bash +$ kubectl get pb -n demo builtin-prom-pb +NAME TYPE VERSION STATUS AGE +builtin-prom-pb kubedb.com/v1 1.18.0 Ready 65s +``` + +KubeDB will create a separate stats service with name `{PgBouncer cr name}-stats` for monitoring purpose. + +```bash +$ kubectl get svc -n demo --selector="app.kubernetes.io/instance=builtin-prom-pb" +NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE +builtin-prom-pb ClusterIP 10.96.124.220 9999/TCP,9595/TCP 2m20s +builtin-prom-pb-pods ClusterIP None 9999/TCP 2m20s +builtin-prom-pb-stats ClusterIP 10.96.132.175 9719/TCP 2m20s +``` + +Here, `builtin-prom-pb-stats` service has been created for monitoring purpose. Let's describe the service. + +```bash +$ kubectl describe svc -n demo builtin-prom-pb-stats +Name: builtin-prom-pb-stats +Namespace: demo +Labels: app.kubernetes.io/component=connection-pooler + app.kubernetes.io/instance=builtin-prom-pb + app.kubernetes.io/managed-by=kubedb.com + app.kubernetes.io/name=pgbouncers.kubedb.com +Annotations: monitoring.appscode.com/agent: prometheus.io/builtin + prometheus.io/path: /metrics + prometheus.io/port: 9719 + prometheus.io/scrape: true +Selector: apb.kubernetes.io/instance=builtin-prom-pb,app.kubernetes.io/managed-by=kubedb.com,app.kubernetes.io/name=pgbouncers.kubedb.com +Type: ClusterIP +IP Family Policy: SingleStack +IP Families: IPv4 +IP: 10.96.132.175 +IPs: 10.96.132.175 +Port: metrics 9719/TCP +TargetPort: metrics/TCP +Endpoints: 10.244.0.27:9719 +Session Affinity: None +Events: +``` + +You can see that the service contains following annotations. + +```bash +prometheus.io/path: /metrics +prometheus.io/port: 9719 +prometheus.io/scrape: true +``` + +The Prometheus server will discover the service endpoint using these specifications and will scrape metrics from the exporter. + +## Configure Prometheus Server + +Now, we have to configure a Prometheus scraping job to scrape the metrics using this service. We are going to configure scraping job similar to this [kubernetes-service-endpoints](https://github.com/appscode/third-party-tools/tree/master/monitoring/prometheus/builtin#kubernetes-service-endpoints) job that scrapes metrics from endpoints of a service. + +Let's configure a Prometheus scraping job to collect metrics from this service. + +```yaml +- job_name: 'kubedb-databases' + honor_labels: true + scheme: http + kubernetes_sd_configs: + - role: endpoints + # by default Prometheus server select all Kubernetes services as possible target. + # relabel_config is used to filter only desired endpoints + relabel_configs: + # keep only those services that has "prometheus.io/scrape","prometheus.io/path" and "prometheus.io/port" anootations + - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scrape, __meta_kubernetes_service_annotation_prometheus_io_port] + separator: ; + regex: true;(.*) + action: keep + # currently KubeDB supported databases uses only "http" scheme to export metrics. so, drop any service that uses "https" scheme. + - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scheme] + action: drop + regex: https + # only keep the stats services created by KubeDB for monitoring purpose which has "-stats" suffix + - source_labels: [__meta_kubernetes_service_name] + separator: ; + regex: (.*-stats) + action: keep + # service created by KubeDB will have "app.kubernetes.io/name" and "app.kubernetes.io/instance" annotations. keep only those services that have these annotations. + - source_labels: [__meta_kubernetes_service_label_app_kubernetes_io_name] + separator: ; + regex: (.*) + action: keep + # read the metric path from "prometheus.io/path: " annotation + - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_path] + action: replace + target_label: __metrics_path__ + regex: (.+) + # read the port from "prometheus.io/port: " annotation and update scraping address accordingly + - source_labels: [__address__, __meta_kubernetes_service_annotation_prometheus_io_port] + action: replace + target_label: __address__ + regex: ([^:]+)(?::\d+)?;(\d+) + replacement: $1:$2 + # add service namespace as label to the scraped metrics + - source_labels: [__meta_kubernetes_namespace] + separator: ; + regex: (.*) + target_label: namespace + replacement: $1 + action: replace + # add service name as a label to the scraped metrics + - source_labels: [__meta_kubernetes_service_name] + separator: ; + regex: (.*) + target_label: service + replacement: $1 + action: replace + # add stats service's labels to the scraped metrics + - action: labelmap + regex: __meta_kubernetes_service_label_(.+) +``` + +### Configure Existing Prometheus Server + +If you already have a Prometheus server running, you have to add above scraping job in the `ConfigMap` used to configure the Prometheus server. Then, you have to restart it for the updated configuration to take effect. + +>If you don't use a persistent volume for Prometheus storage, you will lose your previously scraped data on restart. + +### Deploy New Prometheus Server + +If you don't have any existing Prometheus server running, you have to deploy one. In this section, we are going to deploy a Prometheus server in `monitoring` namespace to collect metrics using this stats service. + +**Create ConfigMap:** + +At first, create a ConfigMap with the scraping configuration. Bellow, the YAML of ConfigMap that we are going to create in this tutorial. + +```yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: prometheus-config + labels: + app: prometheus-demo + namespace: monitoring +data: + prometheus.yml: |- + global: + scrape_interval: 5s + evaluation_interval: 5s + scrape_configs: + - job_name: 'kubedb-databases' + honor_labels: true + scheme: http + kubernetes_sd_configs: + - role: endpoints + # by default Prometheus server select all Kubernetes services as possible target. + # relabel_config is used to filter only desired endpoints + relabel_configs: + # keep only those services that has "prometheus.io/scrape","prometheus.io/path" and "prometheus.io/port" anootations + - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scrape, __meta_kubernetes_service_annotation_prometheus_io_port] + separator: ; + regex: true;(.*) + action: keep + # currently KubeDB supported databases uses only "http" scheme to export metrics. so, drop any service that uses "https" scheme. + - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scheme] + action: drop + regex: https + # only keep the stats services created by KubeDB for monitoring purpose which has "-stats" suffix + - source_labels: [__meta_kubernetes_service_name] + separator: ; + regex: (.*-stats) + action: keep + # service created by KubeDB will have "app.kubernetes.io/name" and "app.kubernetes.io/instance" annotations. keep only those services that have these annotations. + - source_labels: [__meta_kubernetes_service_label_app_kubernetes_io_name] + separator: ; + regex: (.*) + action: keep + # read the metric path from "prometheus.io/path: " annotation + - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_path] + action: replace + target_label: __metrics_path__ + regex: (.+) + # read the port from "prometheus.io/port: " annotation and update scraping address accordingly + - source_labels: [__address__, __meta_kubernetes_service_annotation_prometheus_io_port] + action: replace + target_label: __address__ + regex: ([^:]+)(?::\d+)?;(\d+) + replacement: $1:$2 + # add service namespace as label to the scraped metrics + - source_labels: [__meta_kubernetes_namespace] + separator: ; + regex: (.*) + target_label: namespace + replacement: $1 + action: replace + # add service name as a label to the scraped metrics + - source_labels: [__meta_kubernetes_service_name] + separator: ; + regex: (.*) + target_label: service + replacement: $1 + action: replace + # add stats service's labels to the scraped metrics + - action: labelmap + regex: __meta_kubernetes_service_label_(.+) +``` + +Let's create above `ConfigMap`, + +```bash +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/monitoring/builtin-prometheus/prom-config.yaml +configmap/prometheus-config created +``` + +**Create RBAC:** + +If you are using an RBAC enabled cluster, you have to give necessary RBAC permissions for Prometheus. Let's create necessary RBAC stuffs for Prometheus, + +```bash +$ kubectl apply -f https://github.com/appscode/third-party-tools/raw/master/monitoring/prometheus/builtin/artifacts/rbac.yaml +clusterrole.rbac.authorization.k8s.io/prometheus created +serviceaccount/prometheus created +clusterrolebinding.rbac.authorization.k8s.io/prometheus created +``` + +>YAML for the RBAC resources created above can be found [here](https://github.com/appscode/third-party-tools/blob/master/monitoring/prometheus/builtin/artifacts/rbac.yaml). + +**Deploy Prometheus:** + +Now, we are ready to deploy Prometheus server. We are going to use following [deployment](https://github.com/appscode/third-party-tools/blob/master/monitoring/prometheus/builtin/artifacts/deployment.yaml) to deploy Prometheus server. + +Let's deploy the Prometheus server. + +```bash +$ kubectl apply -f https://github.com/appscode/third-party-tools/raw/master/monitoring/prometheus/builtin/artifacts/deployment.yaml +deployment.apps/prometheus created +``` + +### Verify Monitoring Metrics + +Prometheus server is listening to port `9090`. We are going to use [port forwarding](https://kubernetes.io/docs/tasks/access-application-cluster/port-forward-access-application-cluster/) to access Prometheus dashboard. + +At first, let's check if the Prometheus pod is in `Running` state. + +```bash +$ kubectl get pod -n monitoring -l=app=prometheus +NAME READY STATUS RESTARTS AGE +prometheus-d64b668fb-4khbg 1/1 Running 0 21s +``` + +Now, run following command on a separate terminal to forward 9090 port of `prometheus-d64b668fb-4khbg` pod, + +```bash +$ kubectl port-forward -n monitoring prometheus-d64b668fb-4khbg 9090 +Forwarding from 127.0.0.1:9090 -> 9090 +Forwarding from [::1]:9090 -> 9090 +``` + +Now, we can access the dashboard at `localhost:9090`. Open [http://localhost:9090](http://localhost:9090) in your browser. You should see the endpoint of `builtin-prom-pb-stats` service as one of the targets. + +

+   +

+ +Check the labels marked with red rectangle. These labels confirm that the metrics are coming from `PgBouncer` database `builtin-prom-pb` through stats service `builtin-prom-pb-stats`. + +Now, you can view the collected metrics and create a graph from homepage of this Prometheus dashboard. You can also use this Prometheus server as data source for [Grafana](https://grafana.com/) and create beautiful dashboard with collected metrics. + +## Cleaning up + +To cleanup the Kubernetes resources created by this tutorial, run following commands + +```bash +kubectl delete -n demo pb/builtin-prom-pb + +kubectl delete -n monitoring deployment.apps/prometheus + +kubectl delete -n monitoring clusterrole.rbac.authorization.k8s.io/prometheus +kubectl delete -n monitoring serviceaccount/prometheus +kubectl delete -n monitoring clusterrolebinding.rbac.authorization.k8s.io/prometheus + +kubectl delete ns demo +kubectl delete ns monitoring +``` + +## Next Steps + + +- Monitor your PgBouncer database with KubeDB using [out-of-the-box prometheus-Operator](/docs/guides/pgbouncer/monitoring/using-prometheus-operator.md). +- Detail concepts of [PgBouncer object](/docs/guides/pgbouncer/concepts/pgbouncer.md). +- Detail concepts of [PgBouncerVersion object](/docs/guides/pgbouncer/concepts/catalog.md). +- Want to hack on KubeDB? Check our [contribution guidelines](/docs/CONTRIBUTING.md). diff --git a/docs/guides/pgbouncer/monitoring/using-prometheus-operator.md b/docs/guides/pgbouncer/monitoring/using-prometheus-operator.md new file mode 100644 index 000000000..ba97c0548 --- /dev/null +++ b/docs/guides/pgbouncer/monitoring/using-prometheus-operator.md @@ -0,0 +1,371 @@ +--- +title: Monitor PgBouncer using Prometheus Operator +menu: + docs_{{ .version }}: + identifier: pb-using-prometheus-operator-monitoring + name: Prometheus Operator + parent: pb-monitoring-pgbouncer + weight: 15 +menu_name: docs_{{ .version }} +section_menu_id: guides +--- + +> New to KubeDB? Please start [here](/docs/README.md). + +# Monitoring PgBouncer Using Prometheus operator + +[Prometheus operator](https://github.com/prometheus-operator/prometheus-operator) provides simple and Kubernetes native way to deploy and configure Prometheus server. This tutorial will show you how to use Prometheus operator to monitor PgBouncer database deployed with KubeDB. + +The following diagram shows how KubeDB Provisioner operator monitor `PgBouncer` using Prometheus Operator. Open the image in a new tab to see the enlarged version. + +
+  Monitoring process of PgBouncer using Prometheus Operator +
Fig: Monitoring process of PgBouncer
+
+ +## Before You Begin + +- At first, you need to have a Kubernetes cluster, and the kubectl command-line tool must be configured to communicate with your cluster. If you do not already have a cluster, you can create one by using [kind](https://kind.sigs.k8s.io/docs/user/quick-start/). + +- To learn how Prometheus monitoring works with KubeDB in general, please visit [here](/docs/guides/pgbouncer/monitoring/overview.md). + +- We need a [Prometheus operator](https://github.com/prometheus-operator/prometheus-operator) instance running. If you don't already have a running instance, you can deploy one using this helm chart [here](https://github.com/prometheus-community/helm-charts/tree/main/charts/kube-prometheus-stack). + +- To keep Prometheus resources isolated, we are going to use a separate namespace called `monitoring` to deploy the prometheus operator helm chart. We are going to deploy database in `demo` namespace. + + ```bash + $ kubectl create ns monitoring + namespace/monitoring created + + $ kubectl create ns demo + namespace/demo created + ``` + + + +> Note: YAML files used in this tutorial are stored in [docs/examples/pgbouncer](https://github.com/kubedb/docs/tree/{{< param "info.version" >}}/docs/examples/pgbouncer) folder in GitHub repository [kubedb/docs](https://github.com/kubedb/docs). + +## Find out required labels for ServiceMonitor + +We need to know the labels used to select `ServiceMonitor` by a `Prometheus` crd. We are going to provide these labels in `spec.monitor.prometheus.serviceMonitor.labels` field of PgBouncer crd so that KubeDB creates `ServiceMonitor` object accordingly. + +At first, let's find out the available Prometheus server in our cluster. + +```bash +$ kubectl get prometheus --all-namespaces +NAMESPACE NAME VERSION REPLICAS AGE +monitoring prometheus-kube-prometheus-prometheus v2.39.0 1 13d +``` + +> If you don't have any Prometheus server running in your cluster, deploy one following the guide specified in **Before You Begin** section. + +Now, let's view the YAML of the available Prometheus server `prometheus` in `monitoring` namespace. +```bash +$ kubectl get prometheus -n monitoring prometheus-kube-prometheus-prometheus -o yaml +``` +```yaml +apiVersion: monitoring.coreos.com/v1 +kind: Prometheus +metadata: + annotations: + meta.helm.sh/release-name: prometheus + meta.helm.sh/release-namespace: monitoring + creationTimestamp: "2024-07-15T09:54:08Z" + generation: 1 + labels: + app: kube-prometheus-stack-prometheus + app.kubernetes.io/instance: prometheus + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/part-of: kube-prometheus-stack + app.kubernetes.io/version: 61.2.0 + chart: kube-prometheus-stack-61.2.0 + heritage: Helm + release: prometheus + name: prometheus-kube-prometheus-prometheus + namespace: monitoring + resourceVersion: "83770" + uid: 7144c771-beff-4285-b7a4-bc105c408bd2 +spec: + alerting: + alertmanagers: + - apiVersion: v2 + name: prometheus-kube-prometheus-alertmanager + namespace: monitoring + pathPrefix: / + port: http-web + automountServiceAccountToken: true + enableAdminAPI: false + evaluationInterval: 30s + externalUrl: http://prometheus-kube-prometheus-prometheus.monitoring:9090 + hostNetwork: false + image: quay.io/prometheus/prometheus:v2.53.0 + listenLocal: false + logFormat: logfmt + logLevel: info + paused: false + podMonitorNamespaceSelector: {} + podMonitorSelector: + matchLabels: + release: prometheus + portName: http-web + probeNamespaceSelector: {} + probeSelector: + matchLabels: + release: prometheus + replicas: 1 + retention: 10d + routePrefix: / + ruleNamespaceSelector: {} + ruleSelector: + matchLabels: + release: prometheus + scrapeConfigNamespaceSelector: {} + scrapeConfigSelector: + matchLabels: + release: prometheus + scrapeInterval: 30s + securityContext: + fsGroup: 2000 + runAsGroup: 2000 + runAsNonRoot: true + runAsUser: 1000 + seccompProfile: + type: RuntimeDefault + serviceAccountName: prometheus-kube-prometheus-prometheus + serviceMonitorNamespaceSelector: {} + serviceMonitorSelector: + matchLabels: + release: prometheus + shards: 1 + tsdb: + outOfOrderTimeWindow: 0s + version: v2.53.0 + walCompression: true +status: + availableReplicas: 1 + conditions: + - lastTransitionTime: "2024-07-15T09:56:09Z" + message: "" + observedGeneration: 1 + reason: "" + status: "True" + type: Available + - lastTransitionTime: "2024-07-15T09:56:09Z" + message: "" + observedGeneration: 1 + reason: "" + status: "True" + type: Reconciled + paused: false + replicas: 1 + selector: app.kubernetes.io/instance=prometheus-kube-prometheus-prometheus,app.kubernetes.io/managed-by=prometheus-operator,app.kubernetes.io/name=prometheus,operator.prometheus.io/name=prometheus-kube-prometheus-prometheus,prometheus=prometheus-kube-prometheus-prometheus + shardStatuses: + - availableReplicas: 1 + replicas: 1 + shardID: "0" + unavailableReplicas: 0 + updatedReplicas: 1 + shards: 1 + unavailableReplicas: 0 + updatedReplicas: 1 +``` + +Notice the `spec.serviceMonitorSelector` section. Here, `release: prometheus` label is used to select `ServiceMonitor` crd. So, we are going to use this label in `spec.monitor.prometheus.serviceMonitor.labels` field of PgBouncer crd. + +## Prepare Postgres +Prepare a KubeDB Postgres cluster using this [tutorial](/docs/guides/postgres/clustering/streaming_replication.md), or you can use any externally managed postgres but in that case you need to create an [appbinding](/docs/guides/pgbouncer/concepts/appbinding.md) yourself. In this tutorial we will use 3 node Postgres cluster named `ha-postgres`. + +## Deploy PgBouncer with Monitoring Enabled + +At first, let's deploy an PgBouncer database with monitoring enabled. Below is the PgBouncer object that we are going to create. + +```yaml +apiVersion: kubedb.com/v1 +kind: PgBouncer +metadata: + name: coreos-prom-pb + namespace: demo +spec: + replicas: 1 + version: "1.18.0" + database: + syncUsers: true + databaseName: "postgres" + databaseRef: + name: "ha-postgres" + namespace: demo + connectionPool: + poolMode: session + port: 5432 + deletionPolicy: WipeOut + monitor: + agent: prometheus.io/operator + prometheus: + serviceMonitor: + labels: + release: prometheus + interval: 10s +``` + +Here, + +- `monitor.agent: prometheus.io/operator` indicates that we are going to monitor this server using Prometheus operator. +- `monitor.prometheus.serviceMonitor.labels` specifies that KubeDB should create `ServiceMonitor` with these labels. +- `monitor.prometheus.interval` indicates that the Prometheus server should scrape metrics from this database with 10 seconds interval. + +Let's create the PgBouncer object that we have shown above, + +```bash +$ kubectl create -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/pgbouncer/monitoring/coreos-prom-pb.yaml +pgbouncer.kubedb.com/coreos-prom-pb created +``` + +Now, wait for the database to go into `Running` state. + +```bash +$ kubectl get pb -n demo coreos-prom-pb +NAME TYPE VERSION STATUS AGE +coreos-prom-pb kubedb.com/v1 1.18.0 Ready 65s +``` + +KubeDB will create a separate stats service with name `{PgBouncer crd name}-stats` for monitoring purpose. + +```bash +$ kubectl get svc -n demo --selector="app.kubernetes.io/instance=coreos-prom-pp" +NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE +coreos-prom-pb ClusterIP 10.96.201.180 9999/TCP,9595/TCP 4m3s +coreos-prom-pb-pods ClusterIP None 9999/TCP 4m3s +coreos-prom-pb-stats ClusterIP 10.96.73.22 9719/TCP 4m3s +``` + +Here, `coreos-prom-pb-stats` service has been created for monitoring purpose. + +Let's describe this stats service. + +```bash +$ kubectl describe svc -n demo coreos-prom-pb-stats +``` +```yaml +Name: coreos-prom-pb-stats +Namespace: demo +Labels: app.kubernetes.io/component=connection-pooler + app.kubernetes.io/instance=coreos-prom-pb + app.kubernetes.io/managed-by=kubedb.com + app.kubernetes.io/name=pgbouncers.kubedb.com +Annotations: monitoring.appscode.com/agent: prometheus.io/operator +Selector: app.kubernetes.io/instance=coreos-prom-pb,app.kubernetes.io/managed-by=kubedb.com,app.kubernetes.io/name=pgbouncers.kubedb.com +Type: ClusterIP +IP Family Policy: SingleStack +IP Families: IPv4 +IP: 10.96.73.22 +IPs: 10.96.73.22 +Port: metrics 9719/TCP +TargetPort: metrics/TCP +Endpoints: 10.244.0.26:9719 +Session Affinity: None +Events: +``` + +Notice the `Labels` and `Port` fields. `ServiceMonitor` will use this information to target its endpoints. + +KubeDB will also create a `ServiceMonitor` crd in `demo` namespace that select the endpoints of `coreos-prom-pb-stats` service. Verify that the `ServiceMonitor` crd has been created. + +```bash +$ kubectl get servicemonitor -n demo +NAME AGE +coreos-prom-pb-stats 2m40s +``` + +Let's verify that the `ServiceMonitor` has the label that we had specified in `spec.monitor` section of PgBouncer crd. + +```bash +$ kubectl get servicemonitor -n demo coreos-prom-pb-stats -o yaml +``` +```yaml +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + creationTimestamp: "2024-07-15T10:42:35Z" + generation: 1 + labels: + app.kubernetes.io/component: connection-pooler + app.kubernetes.io/instance: coreos-prom-pb + app.kubernetes.io/managed-by: kubedb.com + app.kubernetes.io/name: pgbouncers.kubedb.com + release: prometheus + name: coreos-prom-pb-stats + namespace: demo + ownerReferences: + - apiVersion: v1 + blockOwnerDeletion: true + controller: true + kind: Service + name: coreos-prom-pb-stats + uid: 844d49bc-dfe4-4ab7-a2dc-b5ec43c3b63e + resourceVersion: "87651" + uid: a7b859d8-306e-4061-9f70-4b57c4b784f7 +spec: + endpoints: + - honorLabels: true + interval: 10s + path: /metrics + port: metrics + namespaceSelector: + matchNames: + - demo + selector: + matchLabels: + app.kubernetes.io/component: connection-pooler + app.kubernetes.io/instance: coreos-prom-pb + app.kubernetes.io/managed-by: kubedb.com + app.kubernetes.io/name: pgbouncers.kubedb.com +``` + +Notice that the `ServiceMonitor` has label `release: prometheus` that we had specified in PgBouncer crd. + +Also notice that the `ServiceMonitor` has selector which match the labels we have seen in the `coreos-prom-pb-stats` service. It also, target the `metrics` port that we have seen in the stats service. + +## Verify Monitoring Metrics + +At first, let's find out the respective Prometheus pod for `prometheus` Prometheus server. + +```bash +$ kubectl get pod -n monitoring -l=app.kubernetes.io/name=prometheus +NAME READY STATUS RESTARTS AGE +prometheus-prometheus-kube-prometheus-prometheus-0 2/2 Running 1 13d +``` + +Prometheus server is listening to port `9090` of `prometheus-prometheus-kube-prometheus-prometheus-0` pod. We are going to use [port forwarding](https://kubernetes.io/docs/tasks/access-application-cluster/port-forward-access-application-cluster/) to access Prometheus dashboard. + +Run following command on a separate terminal to forward the port 9090 of `prometheus-prometheus-kube-prometheus-prometheus-0` pod, + +```bash +$ kubectl port-forward -n monitoring prometheus-prometheus-kube-prometheus-prometheus-0 9090 +Forwarding from 127.0.0.1:9090 -> 9090 +Forwarding from [::1]:9090 -> 9090 +``` + +Now, we can access the dashboard at `localhost:9090`. Open [http://localhost:9090](http://localhost:9090) in your browser. You should see `metrics` endpoint of `coreos-prom-pb-stats` service as one of the targets. + +

+  Prometheus Target +

+ +Check the `endpoint` and `service` labels marked by the red rectangles. It verifies that the target is our expected database. Now, you can view the collected metrics and create a graph from homepage of this Prometheus dashboard. You can also use this Prometheus server as data source for [Grafana](https://grafana.com/) and create a beautiful dashboard with collected metrics. + +## Cleaning up + +To clean up the Kubernetes resources created by this tutorial, run following commands + +```bash +kubectl delete -n demo pb/coreos-prom-pb +kubectl delete -n demo pg/ha-postgres +kubectl delete ns demo +``` + +## Next Steps + +- Monitor your PgBouncer database with KubeDB using [out-of-the-box builtin-Prometheus](/docs/guides/pgbouncer/monitoring/using-builtin-prometheus.md). +- Detail concepts of [PgBouncer object](/docs/guides/pgbouncer/concepts/pgbouncer.md). +- Detail concepts of [PgBouncerVersion object](/docs/guides/pgbouncer/concepts/catalog.md). +- Want to hack on KubeDB? Check our [contribution guidelines](/docs/CONTRIBUTING.md). From 38b3706d1b73cfdc1c32d8dcf6d5334cf1af300c Mon Sep 17 00:00:00 2001 From: Hiranmoy Das Chowdhury Date: Tue, 26 Nov 2024 17:27:31 +0600 Subject: [PATCH 09/29] all completed until custom version Signed-off-by: Hiranmoy Das Chowdhury --- .../pgbouncer/sync-users/pgbouncer-sync.yaml | 15 ++++++++++++ .../examples/pgbouncer/sync-users/secret.yaml | 11 +++++++++ docs/guides/pgbouncer/README.md | 23 ++++++++++++------- docs/guides/pgbouncer/concepts/appbinding.md | 4 +--- docs/guides/pgbouncer/concepts/pgbouncer.md | 2 +- .../guides/pgbouncer/custom-versions/setup.md | 12 +++++----- .../guides/pgbouncer/quickstart/quickstart.md | 4 ++-- .../sync-users/sync-users-pgbouncer.md | 4 ++-- 8 files changed, 53 insertions(+), 22 deletions(-) create mode 100644 docs/examples/pgbouncer/sync-users/pgbouncer-sync.yaml create mode 100644 docs/examples/pgbouncer/sync-users/secret.yaml diff --git a/docs/examples/pgbouncer/sync-users/pgbouncer-sync.yaml b/docs/examples/pgbouncer/sync-users/pgbouncer-sync.yaml new file mode 100644 index 000000000..8aae37078 --- /dev/null +++ b/docs/examples/pgbouncer/sync-users/pgbouncer-sync.yaml @@ -0,0 +1,15 @@ +apiVersion: kubedb.com/v1 +kind: PgBouncer +metadata: + name: pgbouncer-sync + namespace: demo +spec: + version: "1.23.1" + replicas: 1 + database: + syncUsers: true + databaseName: "postgres" + databaseRef: + name: "ha-postgres" + namespace: demo + deletionPolicy: WipeOut \ No newline at end of file diff --git a/docs/examples/pgbouncer/sync-users/secret.yaml b/docs/examples/pgbouncer/sync-users/secret.yaml new file mode 100644 index 000000000..f4b0e8614 --- /dev/null +++ b/docs/examples/pgbouncer/sync-users/secret.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Secret +metadata: + labels: + app.kubernetes.io/instance: ha-postgres + app.kubernetes.io/name: postgreses.kubedb.com + name: sync-secret + namespace: demo +stringData: + password: "12345" + username: "john" \ No newline at end of file diff --git a/docs/guides/pgbouncer/README.md b/docs/guides/pgbouncer/README.md index c3ed534ed..b64cf5f4b 100644 --- a/docs/guides/pgbouncer/README.md +++ b/docs/guides/pgbouncer/README.md @@ -23,14 +23,21 @@ KubeDB operator now comes bundled with PgBouncer crd to handle connection poolin ## PgBouncer Features -| Features | Availability | -|------------------------------------|:------------:| -| Clustering | ✓ | -| Multiple PgBouncer Versions | ✓ | -| Customizable Pooling Configuration | ✓ | -| Custom docker images | ✓ | -| Builtin Prometheus Discovery | ✓ | -| Using Prometheus operator | ✓ | +| Features | Availability | +|-------------------------------------------------------------| :----------: | +| Multiple PgBouncer Versions | ✓ | +| Custom Configuration | ✓ | +| Externally manageable Auth Secret | ✓ | +| Reconfigurable Health Checker | ✓ | +| Integrate with externally managed PostgreSQL | ✓ | +| Sync Postgres Users to PgBouncer | ✓ | +| Custom docker images | ✓ | +| TLS: Add ( [Cert Manager]((https://cert-manager.io/docs/))) | ✓ | +| Monitoring with Prometheus & Grafana | ✓ | +| Builtin Prometheus Discovery | ✓ | +| Using Prometheus operator | ✓ | +| Alert Dashboard | ✓ | +| Grafana Dashboard | ✓ | ## User Guide diff --git a/docs/guides/pgbouncer/concepts/appbinding.md b/docs/guides/pgbouncer/concepts/appbinding.md index 1e1fdb8c4..170a60250 100644 --- a/docs/guides/pgbouncer/concepts/appbinding.md +++ b/docs/guides/pgbouncer/concepts/appbinding.md @@ -20,8 +20,6 @@ An `AppBinding` is a Kubernetes `CustomResourceDefinition`(CRD) which points to If you deploy a database using [KubeDB](https://kubedb.com/docs/latest/welcome/), `AppBinding` object will be created automatically for it. Otherwise, you have to create an `AppBinding` object manually pointing to your desired database. -KubeDB uses [Stash](https://appscode.com/products/stash/) to perform backup/recovery of databases. Stash needs to know how to connect with a target database and the credentials necessary to access it. This is done via an `AppBinding`. - ## AppBinding CRD Specification Like any official Kubernetes resource, an `AppBinding` has `TypeMeta`, `ObjectMeta` and `Spec` sections. However, unlike other Kubernetes resources, it does not have a `Status` section. @@ -71,7 +69,7 @@ An `AppBinding` object has the following fields in the `spec` section: #### spec.type -`spec.type` is an optional field that indicates the type of the app that this `AppBinding` is pointing to. Stash uses this field to resolve the values of `TARGET_APP_TYPE`, `TARGET_APP_GROUP` and `TARGET_APP_RESOURCE` variables of [BackupBlueprint](https://appscode.com/products/stash/latest/concepts/crds/backupblueprint/) object. +`spec.type` is an optional field that indicates the type of the app that this `AppBinding` is pointing to. PgBouncer operator uses this field to recognise the desired postgres database. This field follows the following format: `/`. The above AppBinding is pointing to a `postgres` resource under `kubedb.com` group. diff --git a/docs/guides/pgbouncer/concepts/pgbouncer.md b/docs/guides/pgbouncer/concepts/pgbouncer.md index 40802d1f2..925b4bba1 100644 --- a/docs/guides/pgbouncer/concepts/pgbouncer.md +++ b/docs/guides/pgbouncer/concepts/pgbouncer.md @@ -32,7 +32,7 @@ metadata: namespace: demo spec: version: "1.18.0" - replicas: 2 + replicas: 1 database: syncUsers: true databaseName: "postgres" diff --git a/docs/guides/pgbouncer/custom-versions/setup.md b/docs/guides/pgbouncer/custom-versions/setup.md index 737b2684d..3519ea56e 100644 --- a/docs/guides/pgbouncer/custom-versions/setup.md +++ b/docs/guides/pgbouncer/custom-versions/setup.md @@ -21,7 +21,7 @@ PgBouncerVersions are KubeDB crds that define the docker images KubeDB will use If you want to create a custom image of pgbouncer with additional features, the best way is to build on top of the existing kubedb image. ```docker -FROM kubedb/pgbouncer:1.17.0 +FROM kubedb/pgbouncer:1.23.1 ENV SOME_VERSION_VAR 0.9.1 @@ -32,18 +32,18 @@ RUN set -ex \ bash ``` -From there, we would define a PgBouncerVersion that contains this new image. Let's say we tagged it as `myco/pgbouncer:custom-1.17.0`. You can also build exporter image yourself using [pgbouncer_exporter](https://github.com/kubedb/pgbouncer_exporter) repository. +From there, we would define a PgBouncerVersion that contains this new image. Let's say we tagged it as `myco/pgbouncer:custom-1.23.1`. You can also build exporter image yourself using [pgbouncer_exporter](https://github.com/kubedb/pgbouncer_exporter) repository. ```yaml apiVersion: catalog.kubedb.com/v1alpha1 kind: PgBouncerVersion metadata: - name: "1.17.0" + name: "1.23.1" spec: deprecated: false - version: "1.17.0" + version: "1.23.1" pgBouncer: - image: "myco/pgbouncer:custom-1.17.0" + image: "myco/pgbouncer:custom-1.23.1" exporter: image: "myco/pgbouncer_exporter:v0.1.1" ``` @@ -57,7 +57,7 @@ metadata: name: pgbouncer-server namespace: demo spec: - version: "1.17.0" + version: "1.23.1" replicas: 1 connectionPool: poolMode: session diff --git a/docs/guides/pgbouncer/quickstart/quickstart.md b/docs/guides/pgbouncer/quickstart/quickstart.md index 8cdbceb6e..5b5dcfcc7 100644 --- a/docs/guides/pgbouncer/quickstart/quickstart.md +++ b/docs/guides/pgbouncer/quickstart/quickstart.md @@ -47,6 +47,7 @@ $ kubectl get pgbouncerversions NAME VERSION PGBOUNCER_IMAGE DEPRECATED AGE 1.17.0 1.17.0 ghcr.io/kubedb/pgbouncer:1.17.0 22h 1.18.0 1.18.0 ghcr.io/kubedb/pgbouncer:1.18.0 22h + 1.23.1 1.23.1 ghcr.io/kubedb/pgbouncer:1.23.1 22h ``` @@ -258,9 +259,8 @@ Following table show what KubeDB does when you delete Postgres crd for different | Behavior | DoNotTerminate | Delete | WipeOut | |---------------------------| :------------: | :------: | :------: | | 1. Block Delete operation | ✓ | ✗ | ✗ | -| 2. Delete PetSet | ✗ | ✓ | ✓ | +| 2. Delete PetSet | ✗ | ✓ | ✓ | | 3. Delete Services | ✗ | ✓ | ✓ | -| 4. Delete PVCs | ✗ | ✓ | ✓ | | 5. Delete Secrets | ✗ | ✗ | ✓ | diff --git a/docs/guides/pgbouncer/sync-users/sync-users-pgbouncer.md b/docs/guides/pgbouncer/sync-users/sync-users-pgbouncer.md index 77cb73026..8184e0bc8 100644 --- a/docs/guides/pgbouncer/sync-users/sync-users-pgbouncer.md +++ b/docs/guides/pgbouncer/sync-users/sync-users-pgbouncer.md @@ -100,8 +100,8 @@ Now, wait until `pgbouncer-sync` has status `Ready`. i.e, ```bash $ kubectl get pb -n demo -NAME TYPE VERSION STATUS AGE -pgbouncer-sync kubedb.com/v1 1.18.0 Ready 41s +NAME TYPE VERSION STATUS AGE +pgbouncer-sync kubedb.com/v1 1.18.0 Ready 41s ``` ### Sync Users From 046169a68fb7b0667ecfecc17f69b5caa8919c21 Mon Sep 17 00:00:00 2001 From: Hiranmoy Das Chowdhury Date: Wed, 27 Nov 2024 15:14:07 +0600 Subject: [PATCH 10/29] update version svg Signed-off-by: Hiranmoy Das Chowdhury --- .../pgbouncer/update-version/overview.md | 4 +- .../pgbouncer/update-version.svg | 103 ++++++++++++++++++ 2 files changed, 105 insertions(+), 2 deletions(-) create mode 100644 docs/images/day-2-operation/pgbouncer/update-version.svg diff --git a/docs/guides/pgbouncer/update-version/overview.md b/docs/guides/pgbouncer/update-version/overview.md index 95df6167c..5c6a208dc 100644 --- a/docs/guides/pgbouncer/update-version/overview.md +++ b/docs/guides/pgbouncer/update-version/overview.md @@ -27,8 +27,8 @@ This guide will give you an overview on how KubeDB Ops-manager operator update t The following diagram shows how KubeDB Ops-manager operator used to update the version of `PgBouncer`. Open the image in a new tab to see the enlarged version.
-  updating Process of PgBouncer -
Fig: updating Process of PgBouncer
+  updating Process of Kafka +
Fig: updating Process of Kafka
The updating process consists of the following steps: diff --git a/docs/images/day-2-operation/pgbouncer/update-version.svg b/docs/images/day-2-operation/pgbouncer/update-version.svg new file mode 100644 index 000000000..793adcbc4 --- /dev/null +++ b/docs/images/day-2-operation/pgbouncer/update-version.svg @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From df9ba0d0ad7f92751bef5a6ae18f313c4dbca862 Mon Sep 17 00:00:00 2001 From: Hiranmoy Das Chowdhury Date: Wed, 27 Nov 2024 15:46:10 +0600 Subject: [PATCH 11/29] completed till update-version Signed-off-by: Hiranmoy Das Chowdhury --- .../pgbouncer/update-version/pb-update.yaml | 23 ++++ .../update-version/pbops-update.yaml | 11 ++ .../update-version/update_version.md | 112 ++++++++++-------- 3 files changed, 99 insertions(+), 47 deletions(-) create mode 100644 docs/examples/pgbouncer/update-version/pb-update.yaml create mode 100644 docs/examples/pgbouncer/update-version/pbops-update.yaml diff --git a/docs/examples/pgbouncer/update-version/pb-update.yaml b/docs/examples/pgbouncer/update-version/pb-update.yaml new file mode 100644 index 000000000..c5128d899 --- /dev/null +++ b/docs/examples/pgbouncer/update-version/pb-update.yaml @@ -0,0 +1,23 @@ +apiVersion: kubedb.com/v1 +kind: PgBouncer +metadata: + name: pb-update + namespace: demo +spec: + replicas: 1 + version: "1.18.0" + database: + syncUsers: true + databaseName: "postgres" + databaseRef: + name: "ha-postgres" + namespace: demo + connectionPool: + poolMode: session + port: 5432 + reservePoolSize: 5 + maxClientConnections: 87 + defaultPoolSize: 2 + minPoolSize: 1 + authType: md5 + deletionPolicy: WipeOut \ No newline at end of file diff --git a/docs/examples/pgbouncer/update-version/pbops-update.yaml b/docs/examples/pgbouncer/update-version/pbops-update.yaml new file mode 100644 index 000000000..aef121011 --- /dev/null +++ b/docs/examples/pgbouncer/update-version/pbops-update.yaml @@ -0,0 +1,11 @@ +apiVersion: ops.kubedb.com/v1alpha1 +kind: PgBouncerOpsRequest +metadata: + name: pgbouncer-version-update + namespace: demo +spec: + type: UpdateVersion + databaseRef: + name: pb-update + updateVersion: + targetVersion: 1.23.1 \ No newline at end of file diff --git a/docs/guides/pgbouncer/update-version/update_version.md b/docs/guides/pgbouncer/update-version/update_version.md index 5cc3d7ed4..0a9110d6e 100644 --- a/docs/guides/pgbouncer/update-version/update_version.md +++ b/docs/guides/pgbouncer/update-version/update_version.md @@ -34,7 +34,7 @@ $ kubectl create ns demo namespace/demo created ``` -> **Note:** YAML files used in this tutorial are stored in [docs/examples/pgbouncer](/docs/examples/pgbouncer) directory of [kubedb/docs](https://github.com/kube/docs) repository. +> **Note:** YAML files used in this tutorial are stored in [docs/examples/pgbouncer](/docs/examples/pgbouncer) directory of [kubedb/docs](https://github.com/kubedb/docs) repository. ### Prepare Postgres Prepare a KubeDB Postgres cluster using this [tutorial](/docs/guides/postgres/clustering/streaming_replication.md), or you can use any externally managed postgres but in that case you need to create an [appbinding](/docs/guides/pgbouncer/concepts/appbinding.md) yourself. In this tutorial we will use 3 node Postgres cluster named `ha-postgres`. @@ -150,66 +150,76 @@ Annotations: API Version: ops.kubedb.com/v1alpha1 Kind: PgBouncerOpsRequest Metadata: - Creation Timestamp: 2024-07-17T06:31:58Z + Creation Timestamp: 2024-11-27T09:40:03Z Generation: 1 - Resource Version: 51165 - UID: 1409aec6-3a25-4b2b-90fe-02e5d8b1e8c1 + Resource Version: 41823 + UID: a53940fd-4d2d-4b4b-8ef1-0419dfbce660 Spec: Apply: IfReady Database Ref: Name: pb-update Type: UpdateVersion Update Version: - Target Version: 1.18.0 + Target Version: 1.23.1 Status: Conditions: - Last Transition Time: 2024-07-17T06:31:58Z - Message: PgBouncer ops-request has started to update version + Last Transition Time: 2024-11-27T09:40:03Z + Message: Controller has started to Progress with UpdateVersion of PgBouncerOpsRequest: demo/pgbouncer-version-update Observed Generation: 1 - Reason: UpdateVersion + Reason: Running Status: True - Type: UpdateVersion - Last Transition Time: 2024-07-17T06:32:01Z - Message: Successfully paused database - Observed Generation: 1 - Reason: DatabasePauseSucceeded - Status: True - Type: DatabasePauseSucceeded - Last Transition Time: 2024-07-17T06:32:07Z - Message: successfully reconciled the PgBouncer with updated version + Type: Running + Last Transition Time: 2024-11-27T09:40:08Z + Message: Successfully updated Petset resource Observed Generation: 1 Reason: UpdatePetSets Status: True Type: UpdatePetSets - Last Transition Time: 2024-07-17T06:32:52Z - Message: Successfully Restarted PgBouncer pods - Observed Generation: 1 - Reason: RestartPods - Status: True - Type: RestartPods - Last Transition Time: 2024-07-17T06:32:12Z + Last Transition Time: 2024-11-27T09:40:13Z Message: get pod; ConditionStatus:True; PodName:pb-update-0 Observed Generation: 1 Status: True Type: GetPod--pb-update-0 - Last Transition Time: 2024-07-17T06:32:12Z + Last Transition Time: 2024-11-27T09:40:13Z Message: evict pod; ConditionStatus:True; PodName:pb-update-0 Observed Generation: 1 Status: True Type: EvictPod--pb-update-0 - Last Transition Time: 2024-07-17T06:32:47Z - Message: check pod running; ConditionStatus:True; PodName:pb-update-0 + Last Transition Time: 2024-11-27T09:40:18Z + Message: check replica func; ConditionStatus:True; PodName:pb-update-0 + Observed Generation: 1 + Status: True + Type: CheckReplicaFunc--pb-update-0 + Last Transition Time: 2024-11-27T09:40:18Z + Message: check pod ready; ConditionStatus:True; PodName:pb-update-0 Observed Generation: 1 Status: True - Type: CheckPodRunning--pb-update-0 - Last Transition Time: 2024-07-17T06:32:52Z + Type: CheckPodReady--pb-update-0 + Last Transition Time: 2024-11-27T09:40:48Z + Message: check pg bouncer running; ConditionStatus:True; PodName:pb-update-0 + Observed Generation: 1 + Status: True + Type: CheckPgBouncerRunning--pb-update-0 + Last Transition Time: 2024-11-27T09:40:53Z + Message: Restarting all pods performed successfully in PgBouncer: demo/pb-update for PgBouncerOpsRequest: pgbouncer-version-update + Observed Generation: 1 + Reason: RestartPodsSucceeded + Status: True + Type: RestartPods + Last Transition Time: 2024-11-27T09:41:04Z Message: Successfully updated PgBouncer Observed Generation: 1 Reason: UpdateDatabase Status: True Type: UpdateDatabase - Last Transition Time: 2024-07-17T06:32:52Z - Message: Successfully updated PgBouncer version + Last Transition Time: 2024-11-27T09:41:04Z + Message: Successfully version updated + Observed Generation: 1 + Reason: VersionUpdate + Status: True + Type: VersionUpdate + Last Transition Time: 2024-11-27T09:41:04Z + Message: Controller has successfully completed with UpdateVersion of PgBouncerOpsRequest: demo/pgbouncer-version-update Observed Generation: 1 Reason: Successful Status: True @@ -217,19 +227,27 @@ Status: Observed Generation: 1 Phase: Successful Events: - Type Reason Age From Message - ---- ------ ---- ---- ------- - Normal Starting 2m55s KubeDB Ops-manager Operator Start processing for PgBouncerOpsRequest: demo/pgbouncer-version-update - Normal Starting 2m55s KubeDB Ops-manager Operator Pausing PgBouncer databse: demo/pb-update - Normal Successful 2m55s KubeDB Ops-manager Operator Successfully paused PgBouncer database: demo/pb-update for PgBouncerOpsRequest: pgbouncer-version-update - Normal UpdatePetSets 2m46s KubeDB Ops-manager Operator successfully reconciled the PgBouncer with updated version - Normal get pod; ConditionStatus:True; PodName:pb-update-0 2m41s KubeDB Ops-manager Operator get pod; ConditionStatus:True; PodName:pb-update-0 - Normal evict pod; ConditionStatus:True; PodName:pb-update-0 2m41s KubeDB Ops-manager Operator evict pod; ConditionStatus:True; PodName:pb-update-0 - Normal check pod running; ConditionStatus:False; PodName:pb-update-0 2m36s KubeDB Ops-manager Operator check pod running; ConditionStatus:False; PodName:pb-update-0 - Normal check pod running; ConditionStatus:True; PodName:pb-update-0 2m6s KubeDB Ops-manager Operator check pod running; ConditionStatus:True; PodName:pb-update-0 - Normal RestartPods 2m1s KubeDB Ops-manager Operator Successfully Restarted PgBouncer pods - Normal Starting 2m1s KubeDB Ops-manager Operator Resuming PgBouncer database: demo/pb-update - Normal Successful 2m1s KubeDB Ops-manager Operator Successfully resumed PgBouncer database: demo/pb-update for PgBouncerOpsRequest: pgbouncer-version-update + Type Reason Age From Message + ---- ------ ---- ---- ------- + Normal Starting 114s KubeDB Ops-manager Operator Start processing for PgBouncerOpsRequest: demo/pgbouncer-version-update + Normal Starting 114s KubeDB Ops-manager Operator Pausing PgBouncer databse: demo/pb-update + Normal Successful 114s KubeDB Ops-manager Operator Successfully paused PgBouncer database: demo/pb-update for PgBouncerOpsRequest: pgbouncer-version-update + Warning get pod; ConditionStatus:True; PodName:pb-update-0 104s KubeDB Ops-manager Operator get pod; ConditionStatus:True; PodName:pb-update-0 + Warning evict pod; ConditionStatus:True; PodName:pb-update-0 104s KubeDB Ops-manager Operator evict pod; ConditionStatus:True; PodName:pb-update-0 + Warning check replica func; ConditionStatus:True; PodName:pb-update-0 99s KubeDB Ops-manager Operator check replica func; ConditionStatus:True; PodName:pb-update-0 + Warning check pod ready; ConditionStatus:True; PodName:pb-update-0 99s KubeDB Ops-manager Operator check pod ready; ConditionStatus:True; PodName:pb-update-0 + Warning check pg bouncer running; ConditionStatus:False; PodName:pb-update-0 89s KubeDB Ops-manager Operator check pg bouncer running; ConditionStatus:False; PodName:pb-update-0 + Warning check replica func; ConditionStatus:True; PodName:pb-update-0 89s KubeDB Ops-manager Operator check replica func; ConditionStatus:True; PodName:pb-update-0 + Warning check pod ready; ConditionStatus:True; PodName:pb-update-0 89s KubeDB Ops-manager Operator check pod ready; ConditionStatus:True; PodName:pb-update-0 + Warning check replica func; ConditionStatus:True; PodName:pb-update-0 79s KubeDB Ops-manager Operator check replica func; ConditionStatus:True; PodName:pb-update-0 + Warning check pod ready; ConditionStatus:True; PodName:pb-update-0 79s KubeDB Ops-manager Operator check pod ready; ConditionStatus:True; PodName:pb-update-0 + Warning check replica func; ConditionStatus:True; PodName:pb-update-0 69s KubeDB Ops-manager Operator check replica func; ConditionStatus:True; PodName:pb-update-0 + Warning check pod ready; ConditionStatus:True; PodName:pb-update-0 69s KubeDB Ops-manager Operator check pod ready; ConditionStatus:True; PodName:pb-update-0 + Warning check pg bouncer running; ConditionStatus:True; PodName:pb-update-0 69s KubeDB Ops-manager Operator check pg bouncer running; ConditionStatus:True; PodName:pb-update-0 + Normal Successful 64s KubeDB Ops-manager Operator Restarting all pods performed successfully in PgBouncer: demo/pb-update for PgBouncerOpsRequest: pgbouncer-version-update + Normal Starting 53s KubeDB Ops-manager Operator Resuming PgBouncer database: demo/pb-update + Normal Successful 53s KubeDB Ops-manager Operator Successfully resumed PgBouncer database: demo/pb-update + Normal Successful 53s KubeDB Ops-manager Operator Controller has Successfully updated the version of PgBouncer database: demo/pb-update ``` Now, we are going to verify whether the `PgBouncer` and the related `PetSets` their `Pods` have the new version image. Let's check, @@ -239,10 +257,10 @@ $ kubectl get pb -n demo pb-update -o=jsonpath='{.spec.version}{"\n"}' 1.23.1 $ kubectl get petset -n demo pb-update -o=jsonpath='{.spec.template.spec.containers[0].image}{"\n"}' -mongo:4.0.5 +ghcr.io/kubedb/pgbouncer:1.23.1@sha256:9829a24c60938ab709fe9e039fecd9f0019354edf4e74bfd9e62bb2203e945ee -$ kubectl get pods -n demo mg-standalone-0 -o=jsonpath='{.spec.containers[0].image}{"\n"}' -ghcr.io/appscode-images/pgbouncer:1.23.1@sha256:2697fcad9e11bdc704f6ae0fba85c4451c6b0243140aaaa33e719c3af548bda1 +$ kubectl get pods -n demo pb-update-0 -o=jsonpath='{.spec.containers[0].image}{"\n"}' +ghcr.io/kubedb/pgbouncer:1.23.1@sha256:9829a24c60938ab709fe9e039fecd9f0019354edf4e74bfd9e62bb2203e945ee ``` You can see from above, our `PgBouncer` has been updated with the new version. So, the update process is successfully completed. From 684da43fd1590d88236407f3b8cea36b0a4f8b48 Mon Sep 17 00:00:00 2001 From: Hiranmoy Das Chowdhury Date: Wed, 27 Nov 2024 17:19:46 +0600 Subject: [PATCH 12/29] Horizontal scaling done Signed-off-by: Hiranmoy Das Chowdhury --- .../scaling/horizontal-scaling-down-ops.yaml | 11 + .../scaling/horizontal-scaling-ops.yaml | 11 + .../pgbouncer/scaling/pb-horizontal.yaml | 23 ++ .../horizontal-scaling/horizontal-ops.md | 220 +++++++----------- .../scaling/horizontal-scaling/overview.md | 36 +-- .../pgbouncer/horizontal-scaling.png | Bin 0 -> 44824 bytes 6 files changed, 148 insertions(+), 153 deletions(-) create mode 100644 docs/examples/pgbouncer/scaling/horizontal-scaling-down-ops.yaml create mode 100644 docs/examples/pgbouncer/scaling/horizontal-scaling-ops.yaml create mode 100644 docs/examples/pgbouncer/scaling/pb-horizontal.yaml create mode 100644 docs/images/day-2-operation/pgbouncer/horizontal-scaling.png diff --git a/docs/examples/pgbouncer/scaling/horizontal-scaling-down-ops.yaml b/docs/examples/pgbouncer/scaling/horizontal-scaling-down-ops.yaml new file mode 100644 index 000000000..7447bb29e --- /dev/null +++ b/docs/examples/pgbouncer/scaling/horizontal-scaling-down-ops.yaml @@ -0,0 +1,11 @@ +apiVersion: ops.kubedb.com/v1alpha1 +kind: PgBouncerOpsRequest +metadata: + name: pgbouncer-horizontal-scale-down + namespace: demo +spec: + type: HorizontalScaling + databaseRef: + name: pb-horizontal + horizontalScaling: + replicas: 2 \ No newline at end of file diff --git a/docs/examples/pgbouncer/scaling/horizontal-scaling-ops.yaml b/docs/examples/pgbouncer/scaling/horizontal-scaling-ops.yaml new file mode 100644 index 000000000..75dc3397f --- /dev/null +++ b/docs/examples/pgbouncer/scaling/horizontal-scaling-ops.yaml @@ -0,0 +1,11 @@ +apiVersion: ops.kubedb.com/v1alpha1 +kind: PgBouncerOpsRequest +metadata: + name: pgbouncer-horizontal-scale-up + namespace: demo +spec: + type: HorizontalScaling + databaseRef: + name: pb-horizontal + horizontalScaling: + replicas: 3 \ No newline at end of file diff --git a/docs/examples/pgbouncer/scaling/pb-horizontal.yaml b/docs/examples/pgbouncer/scaling/pb-horizontal.yaml new file mode 100644 index 000000000..512304f10 --- /dev/null +++ b/docs/examples/pgbouncer/scaling/pb-horizontal.yaml @@ -0,0 +1,23 @@ +apiVersion: kubedb.com/v1 +kind: PgBouncer +metadata: + name: pb-horizontal + namespace: demo +spec: + replicas: 1 + version: "1.18.0" + database: + syncUsers: true + databaseName: "postgres" + databaseRef: + name: "ha-postgres" + namespace: demo + connectionPool: + poolMode: session + port: 5432 + reservePoolSize: 5 + maxClientConnections: 87 + defaultPoolSize: 2 + minPoolSize: 1 + authType: md5 + deletionPolicy: WipeOut \ No newline at end of file diff --git a/docs/guides/pgbouncer/scaling/horizontal-scaling/horizontal-ops.md b/docs/guides/pgbouncer/scaling/horizontal-scaling/horizontal-ops.md index 06491c627..f11927fc8 100644 --- a/docs/guides/pgbouncer/scaling/horizontal-scaling/horizontal-ops.md +++ b/docs/guides/pgbouncer/scaling/horizontal-scaling/horizontal-ops.md @@ -87,8 +87,8 @@ Now, wait until `pb-horizontal ` has status `Ready`. i.e, ```bash $ kubectl get pb -n demo -NAME TYPE VERSION STATUS AGE -pb-horizontal kubedb.com/v1 1.18.0 Ready 2m +NAME VERSION STATUS AGE +pb-horizontal 1.18.0 Ready 2m19s ``` Let's check the number of replicas this pgbouncer has from the PgBouncer object, number of pods the petset have, @@ -101,7 +101,7 @@ $ kubectl get petset -n demo pb-horizontal -o json | jq '.spec.replicas' 1 ``` -We can see from both command that the pgbouncer has 3 replicas. +We can see from both command that the pgbouncer has 1 replicas. We are now ready to apply the `PgBouncerOpsRequest` CR to scale this pgbouncer. @@ -136,7 +136,7 @@ Here, Let's create the `PgBouncerOpsRequest` CR we have shown above, ```bash -$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/pgbouncer/scaling/horizontal-scaling/pbops-hscale-up-ops.yaml +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/pgbouncer/scaling/horizontal-scaling-ops.yaml pgbounceropsrequest.ops.kubedb.com/pgbouncer-horizontal-scale-up created ``` @@ -164,91 +164,59 @@ Annotations: API Version: ops.kubedb.com/v1alpha1 Kind: PgBouncerOpsRequest Metadata: - Creation Timestamp: 2024-07-17T08:35:13Z + Creation Timestamp: 2024-11-27T11:12:29Z Generation: 1 - Resource Version: 62002 - UID: ce44f7a1-e78d-4248-a691-62fe1efd11f3 + Resource Version: 49162 + UID: ce390f66-e10f-490f-ad47-f28894d0569a Spec: Apply: IfReady Database Ref: Name: pb-horizontal Horizontal Scaling: - Node: 3 - Type: HorizontalScaling + Replicas: 3 + Type: HorizontalScaling Status: Conditions: - Last Transition Time: 2024-07-17T08:35:13Z - Message: PgBouncer ops-request has started to horizontally scaling the nodes + Last Transition Time: 2024-11-27T11:12:29Z + Message: Controller has started to Progress with HorizontalScaling of PgBouncerOpsRequest: demo/pgbouncer-horizontal-scale-up Observed Generation: 1 - Reason: HorizontalScaling + Reason: Running Status: True - Type: HorizontalScaling - Last Transition Time: 2024-07-17T08:35:16Z - Message: Successfully paused database + Type: Running + Last Transition Time: 2024-11-27T11:12:32Z + Message: Horizontal scaling started in PgBouncer: demo/pb-horizontal for PgBouncerOpsRequest: pgbouncer-horizontal-scale-up Observed Generation: 1 - Reason: DatabasePauseSucceeded + Reason: HorizontalScaleStarted Status: True - Type: DatabasePauseSucceeded - Last Transition Time: 2024-07-17T08:35:41Z - Message: Successfully Scaled Up Node - Observed Generation: 1 - Reason: HorizontalScaleUp - Status: True - Type: HorizontalScaleUp - Last Transition Time: 2024-07-17T08:35:21Z - Message: patch petset; ConditionStatus:True; PodName:pb-horizontal-1 - Observed Generation: 1 - Status: True - Type: PatchPetset--pb-horizontal-1 - Last Transition Time: 2024-07-17T08:35:26Z - Message: is pod ready; ConditionStatus:True; PodName:pb-horizontal-1 - Observed Generation: 1 - Status: True - Type: IsPodReady--pb-horizontal-1 - Last Transition Time: 2024-07-17T08:35:26Z - Message: client failure; ConditionStatus:True; PodName:pb-horizontal-1 - Observed Generation: 1 - Status: True - Type: ClientFailure--pb-horizontal-1 - Last Transition Time: 2024-07-17T08:35:26Z - Message: is node healthy; ConditionStatus:True; PodName:pb-horizontal-1 - Observed Generation: 1 - Status: True - Type: IsNodeHealthy--pb-horizontal-1 - Last Transition Time: 2024-07-17T08:35:31Z - Message: patch petset; ConditionStatus:True; PodName:pb-horizontal-2 - Observed Generation: 1 - Status: True - Type: PatchPetset--pb-horizontal-2 - Last Transition Time: 2024-07-17T08:35:31Z - Message: pb-horizontal already has desired replicas + Type: HorizontalScale + Last Transition Time: 2024-11-27T11:12:37Z + Message: patch p s; ConditionStatus:True; PodName:pb-horizontal-1 Observed Generation: 1 - Reason: HorizontalScale Status: True - Type: HorizontalScale - Last Transition Time: 2024-07-17T08:35:36Z - Message: is pod ready; ConditionStatus:True; PodName:pb-horizontal-2 + Type: PatchPS--pb-horizontal-1 + Last Transition Time: 2024-11-27T11:12:42Z + Message: is pg bouncer running; ConditionStatus:True; PodName:pb-horizontal-1 Observed Generation: 1 Status: True - Type: IsPodReady--pb-horizontal-2 - Last Transition Time: 2024-07-17T08:35:36Z - Message: client failure; ConditionStatus:True; PodName:pb-horizontal-2 + Type: IsPgBouncerRunning--pb-horizontal-1 + Last Transition Time: 2024-11-27T11:12:47Z + Message: patch p s; ConditionStatus:True; PodName:pb-horizontal-2 Observed Generation: 1 Status: True - Type: ClientFailure--pb-horizontal-2 - Last Transition Time: 2024-07-17T08:35:36Z - Message: is node healthy; ConditionStatus:True; PodName:pb-horizontal-2 + Type: PatchPS--pb-horizontal-2 + Last Transition Time: 2024-11-27T11:12:52Z + Message: is pg bouncer running; ConditionStatus:True; PodName:pb-horizontal-2 Observed Generation: 1 Status: True - Type: IsNodeHealthy--pb-horizontal-2 - Last Transition Time: 2024-07-17T08:35:41Z - Message: Successfully updated PgBouncer + Type: IsPgBouncerRunning--pb-horizontal-2 + Last Transition Time: 2024-11-27T11:12:57Z + Message: Horizontal scaling Up performed successfully in PgBouncer: demo/pb-horizontal for PgBouncerOpsRequest: pgbouncer-horizontal-scale-up Observed Generation: 1 - Reason: UpdateDatabase + Reason: HorizontalScaleSucceeded Status: True - Type: UpdateDatabase - Last Transition Time: 2024-07-17T08:35:41Z - Message: Successfully completed horizontally scale pgbouncer cluster + Type: HorizontalScaleUp + Last Transition Time: 2024-11-27T11:13:07Z + Message: Controller has successfully completed with HorizontalScaling of PgBouncerOpsRequest: demo/pgbouncer-horizontal-scale-up Observed Generation: 1 Reason: Successful Status: True @@ -256,21 +224,20 @@ Status: Observed Generation: 1 Phase: Successful Events: - Type Reason Age From Message - ---- ------ ---- ---- ------- - Normal Starting 4m5s KubeDB Ops-manager Operator Start processing for PgBouncerOpsRequest: demo/pgbouncer-horizontal-scale-up - Normal Starting 4m5s KubeDB Ops-manager Operator Pausing PgBouncer databse: demo/pb-horizontal - Normal Successful 4m5s KubeDB Ops-manager Operator Successfully paused PgBouncer database: demo/pb-horizontal for PgBouncerOpsRequest: pgbouncer-horizontal-scale-up - Normal patch petset; ConditionStatus:True; PodName:pb-horizontal-1 3m57s KubeDB Ops-manager Operator patch petset; ConditionStatus:True; PodName:pb-horizontal-1 - Normal is pod ready; ConditionStatus:True; PodName:pb-horizontal-1 3m52s KubeDB Ops-manager Operator is pod ready; ConditionStatus:True; PodName:pb-horizontal-1 - Normal is node healthy; ConditionStatus:True; PodName:pb-horizontal-1 3m52s KubeDB Ops-manager Operator is node healthy; ConditionStatus:True; PodName:pb-horizontal-1 - Normal patch petset; ConditionStatus:True; PodName:pb-horizontal-2 3m47s KubeDB Ops-manager Operator patch petset; ConditionStatus:True; PodName:pb-horizontal-2 - Normal is pod ready; ConditionStatus:True; PodName:pb-horizontal-2 3m42s KubeDB Ops-manager Operator is pod ready; ConditionStatus:True; PodName:pb-horizontal-2 - Normal is node healthy; ConditionStatus:True; PodName:pb-horizontal-2 3m42s KubeDB Ops-manager Operator is node healthy; ConditionStatus:True; PodName:pb-horizontal-2 - Normal HorizontalScaleUp 3m37s KubeDB Ops-manager Operator Successfully Scaled Up Node - Normal UpdateDatabase 3m37s KubeDB Ops-manager Operator Successfully updated PgBouncer - Normal Starting 3m37s KubeDB Ops-manager Operator Resuming PgBouncer database: demo/pb-horizontal - Normal Successful 3m37s KubeDB Ops-manager Operator Successfully resumed PgBouncer database: demo/pb-horizontal for PgBouncerOpsRequest: pgbouncer-horizontal-scale-up + Type Reason Age From Message + ---- ------ ---- ---- ------- + Normal Starting 2m13s KubeDB Ops-manager Operator Start processing for PgBouncerOpsRequest: demo/pgbouncer-horizontal-scale-up + Normal Starting 2m13s KubeDB Ops-manager Operator Pausing PgBouncer databse: demo/pb-horizontal + Normal Successful 2m13s KubeDB Ops-manager Operator Successfully paused PgBouncer database: demo/pb-horizontal for PgBouncerOpsRequest: pgbouncer-horizontal-scale-up + Normal Starting 2m10s KubeDB Ops-manager Operator Horizontal scaling started in PgBouncer: demo/pb-horizontal for PgBouncerOpsRequest: pgbouncer-horizontal-scale-up + Warning patch p s; ConditionStatus:True; PodName:pb-horizontal-1 2m5s KubeDB Ops-manager Operator patch p s; ConditionStatus:True; PodName:pb-horizontal-1 + Warning is pg bouncer running; ConditionStatus:True; PodName:pb-horizontal-1 2m KubeDB Ops-manager Operator is pg bouncer running; ConditionStatus:True; PodName:pb-horizontal-1 + Warning patch p s; ConditionStatus:True; PodName:pb-horizontal-2 115s KubeDB Ops-manager Operator patch p s; ConditionStatus:True; PodName:pb-horizontal-2 + Warning is pg bouncer running; ConditionStatus:True; PodName:pb-horizontal-2 110s KubeDB Ops-manager Operator is pg bouncer running; ConditionStatus:True; PodName:pb-horizontal-2 + Normal Successful 105s KubeDB Ops-manager Operator Horizontal scaling Up performed successfully in PgBouncer: demo/pb-horizontal for PgBouncerOpsRequest: pgbouncer-horizontal-scale-up + Normal Starting 95s KubeDB Ops-manager Operator Resuming PgBouncer database: demo/pb-horizontal + Normal Successful 95s KubeDB Ops-manager Operator Successfully resumed PgBouncer database: demo/pb-horizontal + Normal Successful 95s KubeDB Ops-manager Operator Controller has Successfully scaled the PgBouncer database: demo/pb-horizontal ``` Now, we are going to verify the number of replicas this pgbouncer has from the PgBouncer object, number of pods the petset have, @@ -316,7 +283,7 @@ Here, Let's create the `PgBouncerOpsRequest` CR we have shown above, ```bash -$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/pgbouncer/scaling/horizontal-scaling/pbops-hscale-down-ops.yaml +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/pgbouncer/scaling/horizontal-scaling-down-ops.yaml pgbounceropsrequest.ops.kubedb.com/pgbouncer-horizontal-scale-down created ``` @@ -344,66 +311,49 @@ Annotations: API Version: ops.kubedb.com/v1alpha1 Kind: PgBouncerOpsRequest Metadata: - Creation Timestamp: 2024-07-17T08:52:28Z + Creation Timestamp: 2024-11-27T11:16:05Z Generation: 1 - Resource Version: 63600 - UID: 019f9d8f-c2b0-4154-b3d3-b715b8805fd7 + Resource Version: 49481 + UID: cf4bc042-8316-4dce-b6a2-60981af7f4db Spec: Apply: IfReady Database Ref: Name: pb-horizontal Horizontal Scaling: - Node: 2 - Type: HorizontalScaling + Replicas: 2 + Type: HorizontalScaling Status: Conditions: - Last Transition Time: 2024-07-17T08:52:28Z - Message: PgBouncer ops-request has started to horizontally scaling the nodes - Observed Generation: 1 - Reason: HorizontalScaling - Status: True - Type: HorizontalScaling - Last Transition Time: 2024-07-17T08:52:31Z - Message: Successfully paused database - Observed Generation: 1 - Reason: DatabasePauseSucceeded - Status: True - Type: DatabasePauseSucceeded - Last Transition Time: 2024-07-17T08:53:16Z - Message: Successfully Scaled Down Node - Observed Generation: 1 - Reason: HorizontalScaleDown - Status: True - Type: HorizontalScaleDown - Last Transition Time: 2024-07-17T08:52:36Z - Message: patch petset; ConditionStatus:True; PodName:pb-horizontal-2 + Last Transition Time: 2024-11-27T11:16:05Z + Message: Controller has started to Progress with HorizontalScaling of PgBouncerOpsRequest: demo/pgbouncer-horizontal-scale-down Observed Generation: 1 + Reason: Running Status: True - Type: PatchPetset--pb-horizontal-2 - Last Transition Time: 2024-07-17T08:52:36Z - Message: pb-horizontal already has desired replicas + Type: Running + Last Transition Time: 2024-11-27T11:16:08Z + Message: Horizontal scaling started in PgBouncer: demo/pb-horizontal for PgBouncerOpsRequest: pgbouncer-horizontal-scale-down Observed Generation: 1 - Reason: HorizontalScale + Reason: HorizontalScaleStarted Status: True Type: HorizontalScale - Last Transition Time: 2024-07-17T08:52:41Z - Message: get pod; ConditionStatus:False + Last Transition Time: 2024-11-27T11:16:13Z + Message: patch p s; ConditionStatus:True; PodName:pb-horizontal-3 Observed Generation: 1 - Status: False - Type: GetPod - Last Transition Time: 2024-07-17T08:53:11Z + Status: True + Type: PatchPS--pb-horizontal-3 + Last Transition Time: 2024-11-27T11:16:18Z Message: get pod; ConditionStatus:True; PodName:pb-horizontal-2 Observed Generation: 1 Status: True Type: GetPod--pb-horizontal-2 - Last Transition Time: 2024-07-17T08:53:16Z - Message: Successfully updated PgBouncer + Last Transition Time: 2024-11-27T11:16:23Z + Message: Horizontal scaling down performed successfully in PgBouncer: demo/pb-horizontal for PgBouncerOpsRequest: pgbouncer-horizontal-scale-down Observed Generation: 1 - Reason: UpdateDatabase + Reason: HorizontalScaleSucceeded Status: True - Type: UpdateDatabase - Last Transition Time: 2024-07-17T08:53:16Z - Message: Successfully completed horizontally scale pgbouncer cluster + Type: HorizontalScaleDown + Last Transition Time: 2024-11-27T11:16:33Z + Message: Controller has successfully completed with HorizontalScaling of PgBouncerOpsRequest: demo/pgbouncer-horizontal-scale-down Observed Generation: 1 Reason: Successful Status: True @@ -411,18 +361,18 @@ Status: Observed Generation: 1 Phase: Successful Events: - Type Reason Age From Message - ---- ------ ---- ---- ------- - Normal Starting 96s KubeDB Ops-manager Operator Start processing for PgBouncerOpsRequest: demo/pgbouncer-horizontal-scale-down - Normal Starting 96s KubeDB Ops-manager Operator Pausing PgBouncer databse: demo/pb-horizontal - Normal Successful 96s KubeDB Ops-manager Operator Successfully paused PgBouncer database: demo/pb-horizontal for PgBouncerOpsRequest: pgbouncer-horizontal-scale-down - Normal patch petset; ConditionStatus:True; PodName:pb-horizontal-2 88s KubeDB Ops-manager Operator patch petset; ConditionStatus:True; PodName:pb-horizontal-2 - Normal get pod; ConditionStatus:False 83s KubeDB Ops-manager Operator get pod; ConditionStatus:False - Normal get pod; ConditionStatus:True; PodName:pb-horizontal-2 53s KubeDB Ops-manager Operator get pod; ConditionStatus:True; PodName:pb-horizontal-2 - Normal HorizontalScaleDown 48s KubeDB Ops-manager Operator Successfully Scaled Down Node - Normal UpdateDatabase 48s KubeDB Ops-manager Operator Successfully updated PgBouncer - Normal Starting 48s KubeDB Ops-manager Operator Resuming PgBouncer database: demo/pb-horizontal - Normal Successful 48s KubeDB Ops-manager Operator Successfully resumed PgBouncer database: demo/pb-horizontal for PgBouncerOpsRequest: pgbouncer-horizontal-scale-down + Type Reason Age From Message + ---- ------ ---- ---- ------- + Normal Starting 2m38s KubeDB Ops-manager Operator Start processing for PgBouncerOpsRequest: demo/pgbouncer-horizontal-scale-down + Normal Starting 2m38s KubeDB Ops-manager Operator Pausing PgBouncer databse: demo/pb-horizontal + Normal Successful 2m38s KubeDB Ops-manager Operator Successfully paused PgBouncer database: demo/pb-horizontal for PgBouncerOpsRequest: pgbouncer-horizontal-scale-down + Normal Starting 2m35s KubeDB Ops-manager Operator Horizontal scaling started in PgBouncer: demo/pb-horizontal for PgBouncerOpsRequest: pgbouncer-horizontal-scale-down + Warning patch p s; ConditionStatus:True; PodName:pb-horizontal-3 2m30s KubeDB Ops-manager Operator patch p s; ConditionStatus:True; PodName:pb-horizontal-3 + Warning get pod; ConditionStatus:True; PodName:pb-horizontal-2 2m25s KubeDB Ops-manager Operator get pod; ConditionStatus:True; PodName:pb-horizontal-2 + Normal Successful 2m20s KubeDB Ops-manager Operator Horizontal scaling down performed successfully in PgBouncer: demo/pb-horizontal for PgBouncerOpsRequest: pgbouncer-horizontal-scale-down + Normal Starting 2m10s KubeDB Ops-manager Operator Resuming PgBouncer database: demo/pb-horizontal + Normal Successful 2m10s KubeDB Ops-manager Operator Successfully resumed PgBouncer database: demo/pb-horizontal + Normal Successful 2m10s KubeDB Ops-manager Operator Controller has Successfully scaled the PgBouncer database: demo/pb-horizontal ``` Now, we are going to verify the number of replicas this pgbouncer has from the PgBouncer object, number of pods the petset have, diff --git a/docs/guides/pgbouncer/scaling/horizontal-scaling/overview.md b/docs/guides/pgbouncer/scaling/horizontal-scaling/overview.md index 06eeb25f8..44681355b 100644 --- a/docs/guides/pgbouncer/scaling/horizontal-scaling/overview.md +++ b/docs/guides/pgbouncer/scaling/horizontal-scaling/overview.md @@ -1,5 +1,5 @@ --- -title: Pgpool Horizontal Scaling Overview +title: PgBouncer Horizontal Scaling Overview menu: docs_{{ .version }}: identifier: pb-horizontal-scaling-overview @@ -12,43 +12,43 @@ section_menu_id: guides > New to KubeDB? Please start [here](/docs/README.md). -# Pgpool Horizontal Scaling +# PgBouncer Horizontal Scaling -This guide will give an overview on how KubeDB Ops-manager operator scales up or down `Pgpool` replicas of PetSet. +This guide will give an overview on how KubeDB Ops-manager operator scales up or down `PgBouncer` replicas of PetSet. ## Before You Begin - You should be familiar with the following `KubeDB` concepts: - - [Pgpool](/docs/guides/pgpool/concepts/pgpool.md) - - [PgpoolOpsRequest](/docs/guides/pgpool/concepts/opsrequest.md) + - [PgBouncer](/docs/guides/pgbouncer/concepts/pgbouncer.md) + - [PgBouncerOpsRequest](/docs/guides/pgbouncer/concepts/opsrequest.md) ## How Horizontal Scaling Process Works -The following diagram shows how KubeDB Ops-manager operator scales up or down `Pgpool` database components. Open the image in a new tab to see the enlarged version. +The following diagram shows how KubeDB Ops-manager operator scales up or down `PgBouncer` database components. Open the image in a new tab to see the enlarged version.
-  Horizontal scaling process of Pgpool -
Fig: Horizontal scaling process of Pgpool
+  Horizontal scaling process of PgBouncer +
Fig: Horizontal scaling process of PgBouncer
The Horizontal scaling process consists of the following steps: -1. At first, a user creates a `Pgpool` Custom Resource (CR). +1. At first, a user creates a `PgBouncer` Custom Resource (CR). -2. `KubeDB` Provisioner operator watches the `Pgpool` CR. +2. `KubeDB` Provisioner operator watches the `PgBouncer` CR. -3. When the operator finds a `Pgpool` CR, it creates `PetSet` and related necessary stuff like secrets, services, etc. +3. When the operator finds a `PgBouncer` CR, it creates `PetSet` and related necessary stuff like secrets, services, etc. -4. Then, in order to scale the `PetSet` of the `Pgpool` database the user creates a `PgpoolOpsRequest` CR with desired information. +4. Then, in order to scale the `PetSet` of the `PgBouncer` database the user creates a `PgBouncerOpsRequest` CR with desired information. -5. `KubeDB` Ops-manager operator watches the `PgpoolOpsRequest` CR. +5. `KubeDB` Ops-manager operator watches the `PgBouncerOpsRequest` CR. -6. When it finds a `PgpoolOpsRequest` CR, it pauses the `Pgpool` object which is referred from the `PgpoolOpsRequest`. So, the `KubeDB` Provisioner operator doesn't perform any operations on the `Pgpool` object during the horizontal scaling process. +6. When it finds a `PgBouncerOpsRequest` CR, it pauses the `PgBouncer` object which is referred from the `PgBouncerOpsRequest`. So, the `KubeDB` Provisioner operator doesn't perform any operations on the `PgBouncer` object during the horizontal scaling process. -7. Then the `KubeDB` Ops-manager operator will scale the related PetSet Pods to reach the expected number of replicas defined in the `PgpoolOpsRequest` CR. +7. Then the `KubeDB` Ops-manager operator will scale the related PetSet Pods to reach the expected number of replicas defined in the `PgBouncerOpsRequest` CR. -8. After the successfully scaling the replicas of the related PetSet Pods, the `KubeDB` Ops-manager operator updates the number of replicas in the `Pgpool` object to reflect the updated state. +8. After the successfully scaling the replicas of the related PetSet Pods, the `KubeDB` Ops-manager operator updates the number of replicas in the `PgBouncer` object to reflect the updated state. -9. After the successful scaling of the `Pgpool` replicas, the `KubeDB` Ops-manager operator resumes the `Pgpool` object so that the `KubeDB` Provisioner operator resumes its usual operations. +9. After the successful scaling of the `PgBouncer` replicas, the `KubeDB` Ops-manager operator resumes the `PgBouncer` object so that the `KubeDB` Provisioner operator resumes its usual operations. -In the next docs, we are going to show a step-by-step guide on horizontal scaling of Pgpool using `PgpoolOpsRequest` CRD. \ No newline at end of file +In the next docs, we are going to show a step-by-step guide on horizontal scaling of PgBouncer using `PgBouncerOpsRequest` CRD. \ No newline at end of file diff --git a/docs/images/day-2-operation/pgbouncer/horizontal-scaling.png b/docs/images/day-2-operation/pgbouncer/horizontal-scaling.png new file mode 100644 index 0000000000000000000000000000000000000000..9ae39092c6f7ddcc2a4d68280446fc7fe8d223e9 GIT binary patch literal 44824 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 i(?8PO3BZ93CsN-cM5|pABohHol08tTxz|4+97 literal 0 HcmV?d00001 From 3d680d40d0003c284a2dbda80e2cffeeebf6ee5a Mon Sep 17 00:00:00 2001 From: Hiranmoy Das Chowdhury Date: Wed, 27 Nov 2024 18:37:56 +0600 Subject: [PATCH 13/29] vertical scaling done fully Signed-off-by: Hiranmoy Das Chowdhury --- .../pgbouncer/scaling/pb-vertical-ops.yaml | 20 ++++ .../pgbouncer/scaling/pb-vertical.yaml | 23 ++++ .../scaling/vertical-scaling/overview.md | 2 +- .../scaling/vertical-scaling/vertical-ops.md | 99 +++++++++-------- .../pgbouncer/vertical-scaling.svg | 104 ++++++++++++++++++ 5 files changed, 203 insertions(+), 45 deletions(-) create mode 100644 docs/examples/pgbouncer/scaling/pb-vertical-ops.yaml create mode 100644 docs/examples/pgbouncer/scaling/pb-vertical.yaml create mode 100644 docs/images/day-2-operation/pgbouncer/vertical-scaling.svg diff --git a/docs/examples/pgbouncer/scaling/pb-vertical-ops.yaml b/docs/examples/pgbouncer/scaling/pb-vertical-ops.yaml new file mode 100644 index 000000000..cf24a4b11 --- /dev/null +++ b/docs/examples/pgbouncer/scaling/pb-vertical-ops.yaml @@ -0,0 +1,20 @@ +apiVersion: ops.kubedb.com/v1alpha1 +kind: PgBouncerOpsRequest +metadata: + name: pgbouncer-scale-vertical + namespace: demo +spec: + type: VerticalScaling + databaseRef: + name: pb-vertical + verticalScaling: + pgbouncer: + resources: + requests: + memory: "2Gi" + cpu: "1" + limits: + memory: "2Gi" + cpu: "1" + timeout: 5m + apply: IfReady \ No newline at end of file diff --git a/docs/examples/pgbouncer/scaling/pb-vertical.yaml b/docs/examples/pgbouncer/scaling/pb-vertical.yaml new file mode 100644 index 000000000..97c25040b --- /dev/null +++ b/docs/examples/pgbouncer/scaling/pb-vertical.yaml @@ -0,0 +1,23 @@ +apiVersion: kubedb.com/v1 +kind: PgBouncer +metadata: + name: pb-vertical + namespace: demo +spec: + replicas: 1 + version: "1.18.0" + database: + syncUsers: true + databaseName: "postgres" + databaseRef: + name: "ha-postgres" + namespace: demo + connectionPool: + poolMode: session + port: 5432 + reservePoolSize: 5 + maxClientConnections: 87 + defaultPoolSize: 2 + minPoolSize: 1 + authType: md5 + deletionPolicy: WipeOut \ No newline at end of file diff --git a/docs/guides/pgbouncer/scaling/vertical-scaling/overview.md b/docs/guides/pgbouncer/scaling/vertical-scaling/overview.md index 5ec7f24c8..77839a614 100644 --- a/docs/guides/pgbouncer/scaling/vertical-scaling/overview.md +++ b/docs/guides/pgbouncer/scaling/vertical-scaling/overview.md @@ -27,7 +27,7 @@ This guide will give an overview on how KubeDB Ops-manager operator updates the The following diagram shows how KubeDB Ops-manager operator updates the resources of the `PgBouncer`. Open the image in a new tab to see the enlarged version.
-  Vertical scaling process of PgBouncer +  Vertical scaling process of PgBouncer
Fig: Vertical scaling process of PgBouncer
diff --git a/docs/guides/pgbouncer/scaling/vertical-scaling/vertical-ops.md b/docs/guides/pgbouncer/scaling/vertical-scaling/vertical-ops.md index 25c508356..eb3bb8da0 100644 --- a/docs/guides/pgbouncer/scaling/vertical-scaling/vertical-ops.md +++ b/docs/guides/pgbouncer/scaling/vertical-scaling/vertical-ops.md @@ -98,7 +98,6 @@ Let's check the Pod containers resources, $ kubectl get pod -n demo pb-vertical-0 -o json | jq '.spec.containers[].resources' { "limits": { - "cpu": "500m", "memory": "1Gi" }, "requests": { @@ -153,7 +152,7 @@ Here, Let's create the `PgBouncerOpsRequest` CR we have shown above, ```bash -$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/pgbouncer/scaling/vertical-scaling/pb-vertical-ops.yaml +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/pgbouncer/scaling/pb-vertical-ops.yaml pgbounceropsrequest.ops.kubedb.com/pgbouncer-scale-vertical created ``` @@ -181,10 +180,10 @@ Annotations: API Version: ops.kubedb.com/v1alpha1 Kind: PgBouncerOpsRequest Metadata: - Creation Timestamp: 2024-07-17T09:44:22Z + Creation Timestamp: 2024-11-27T12:35:02Z Generation: 1 - Resource Version: 68270 - UID: 62a105f7-e7b9-444e-9303-79818fccfdef + Resource Version: 55854 + UID: 567e12f9-b561-4fea-af91-1ed9412a0d74 Spec: Apply: IfReady Database Ref: @@ -192,7 +191,7 @@ Spec: Timeout: 5m Type: VerticalScaling Vertical Scaling: - Node: + Pgbouncer: Resources: Limits: Cpu: 1 @@ -202,47 +201,51 @@ Spec: Memory: 2Gi Status: Conditions: - Last Transition Time: 2024-07-17T09:44:22Z - Message: PgBouncer ops-request has started to vertically scaling the PgBouncer nodes - Observed Generation: 1 - Reason: VerticalScaling - Status: True - Type: VerticalScaling - Last Transition Time: 2024-07-17T09:44:25Z - Message: Successfully paused database + Last Transition Time: 2024-11-27T12:35:02Z + Message: Controller has started to Progress with VerticalScaling of PgBouncerOpsRequest: demo/pgbouncer-scale-vertical Observed Generation: 1 - Reason: DatabasePauseSucceeded + Reason: Running Status: True - Type: DatabasePauseSucceeded - Last Transition Time: 2024-07-17T09:44:25Z - Message: Successfully updated PetSets Resources + Type: Running + Last Transition Time: 2024-11-27T12:35:08Z + Message: Successfully updated Petset resource Observed Generation: 1 Reason: UpdatePetSets Status: True Type: UpdatePetSets - Last Transition Time: 2024-07-17T09:45:10Z - Message: Successfully Restarted Pods With Resources - Observed Generation: 1 - Reason: RestartPods - Status: True - Type: RestartPods - Last Transition Time: 2024-07-17T09:44:30Z + Last Transition Time: 2024-11-27T12:35:13Z Message: get pod; ConditionStatus:True; PodName:pb-vertical-0 Observed Generation: 1 Status: True Type: GetPod--pb-vertical-0 - Last Transition Time: 2024-07-17T09:44:30Z + Last Transition Time: 2024-11-27T12:35:13Z Message: evict pod; ConditionStatus:True; PodName:pb-vertical-0 Observed Generation: 1 Status: True Type: EvictPod--pb-vertical-0 - Last Transition Time: 2024-07-17T09:45:05Z - Message: check pod running; ConditionStatus:True; PodName:pb-vertical-0 + Last Transition Time: 2024-11-27T12:35:18Z + Message: check replica func; ConditionStatus:True; PodName:pb-vertical-0 + Observed Generation: 1 + Status: True + Type: CheckReplicaFunc--pb-vertical-0 + Last Transition Time: 2024-11-27T12:35:18Z + Message: check pod ready; ConditionStatus:True; PodName:pb-vertical-0 + Observed Generation: 1 + Status: True + Type: CheckPodReady--pb-vertical-0 + Last Transition Time: 2024-11-27T12:35:38Z + Message: check pg bouncer running; ConditionStatus:True; PodName:pb-vertical-0 + Observed Generation: 1 + Status: True + Type: CheckPgBouncerRunning--pb-vertical-0 + Last Transition Time: 2024-11-27T12:35:43Z + Message: Vertical scaling Up performed successfully in PgBouncer: demo/pb-vertical for PgBouncerOpsRequest: pgbouncer-scale-vertical Observed Generation: 1 + Reason: VerticalScaleSucceeded Status: True - Type: CheckPodRunning--pb-vertical-0 - Last Transition Time: 2024-07-17T09:45:10Z - Message: Successfully completed the vertical scaling for PgBouncer + Type: VerticalScale + Last Transition Time: 2024-11-27T12:35:53Z + Message: Controller has successfully completed with VerticalScaling of PgBouncerOpsRequest: demo/pgbouncer-scale-vertical Observed Generation: 1 Reason: Successful Status: True @@ -250,19 +253,27 @@ Status: Observed Generation: 1 Phase: Successful Events: - Type Reason Age From Message - ---- ------ ---- ---- ------- - Normal Starting 4m16s KubeDB Ops-manager Operator Start processing for PgBouncerOpsRequest: demo/pgbouncer-scale-vertical - Normal Starting 4m16s KubeDB Ops-manager Operator Pausing PgBouncer databse: demo/pb-vertical - Normal Successful 4m16s KubeDB Ops-manager Operator Successfully paused PgBouncer database: demo/pb-vertical for PgBouncerOpsRequest: pgbouncer-scale-vertical - Normal UpdatePetSets 4m13s KubeDB Ops-manager Operator Successfully updated PetSets Resources - Warning get pod; ConditionStatus:True; PodName:pb-vertical-0 4m8s KubeDB Ops-manager Operator get pod; ConditionStatus:True; PodName:pb-vertical-0 - Warning evict pod; ConditionStatus:True; PodName:pb-vertical-0 4m8s KubeDB Ops-manager Operator evict pod; ConditionStatus:True; PodName:pb-vertical-0 - Warning check pod running; ConditionStatus:False; PodName:pb-vertical-0 4m3s KubeDB Ops-manager Operator check pod running; ConditionStatus:False; PodName:pb-vertical-0 - Warning check pod running; ConditionStatus:True; PodName:pb-vertical-0 3m33s KubeDB Ops-manager Operator check pod running; ConditionStatus:True; PodName:pb-vertical-0 - Normal RestartPods 3m28s KubeDB Ops-manager Operator Successfully Restarted Pods With Resources - Normal Starting 3m28s KubeDB Ops-manager Operator Resuming PgBouncer database: demo/pb-vertical - Normal Successful 3m28s KubeDB Ops-manager Operator Successfully resumed PgBouncer database: demo/pb-vertical for PgBouncerOpsRequest: pgbouncer-scale-vertical + Type Reason Age From Message + ---- ------ ---- ---- ------- + Normal Starting 81s KubeDB Ops-manager Operator Start processing for PgBouncerOpsRequest: demo/pgbouncer-scale-vertical + Normal Starting 81s KubeDB Ops-manager Operator Pausing PgBouncer databse: demo/pb-vertical + Normal Successful 81s KubeDB Ops-manager Operator Successfully paused PgBouncer database: demo/pb-vertical for PgBouncerOpsRequest: pgbouncer-scale-vertical + Warning get pod; ConditionStatus:True; PodName:pb-vertical-0 70s KubeDB Ops-manager Operator get pod; ConditionStatus:True; PodName:pb-vertical-0 + Warning evict pod; ConditionStatus:True; PodName:pb-vertical-0 70s KubeDB Ops-manager Operator evict pod; ConditionStatus:True; PodName:pb-vertical-0 + Warning check replica func; ConditionStatus:True; PodName:pb-vertical-0 65s KubeDB Ops-manager Operator check replica func; ConditionStatus:True; PodName:pb-vertical-0 + Warning check pod ready; ConditionStatus:True; PodName:pb-vertical-0 65s KubeDB Ops-manager Operator check pod ready; ConditionStatus:True; PodName:pb-vertical-0 + Warning check pg bouncer running; ConditionStatus:False; PodName:pb-vertical-0 55s KubeDB Ops-manager Operator check pg bouncer running; ConditionStatus:False; PodName:pb-vertical-0 + Warning check replica func; ConditionStatus:True; PodName:pb-vertical-0 55s KubeDB Ops-manager Operator check replica func; ConditionStatus:True; PodName:pb-vertical-0 + Warning check pod ready; ConditionStatus:True; PodName:pb-vertical-0 55s KubeDB Ops-manager Operator check pod ready; ConditionStatus:True; PodName:pb-vertical-0 + Warning check replica func; ConditionStatus:True; PodName:pb-vertical-0 45s KubeDB Ops-manager Operator check replica func; ConditionStatus:True; PodName:pb-vertical-0 + Warning check pod ready; ConditionStatus:True; PodName:pb-vertical-0 45s KubeDB Ops-manager Operator check pod ready; ConditionStatus:True; PodName:pb-vertical-0 + Warning check pg bouncer running; ConditionStatus:True; PodName:pb-vertical-0 45s KubeDB Ops-manager Operator check pg bouncer running; ConditionStatus:True; PodName:pb-vertical-0 + Normal Successful 40s KubeDB Ops-manager Operator Vertical scaling Up performed successfully in PgBouncer: demo/pb-vertical for PgBouncerOpsRequest: pgbouncer-scale-vertical + Normal Starting 30s KubeDB Ops-manager Operator Resuming PgBouncer database: demo/pb-vertical + Normal Successful 30s KubeDB Ops-manager Operator Successfully resumed PgBouncer database: demo/pb-vertical + Normal Starting 30s KubeDB Ops-manager Operator Resuming PgBouncer database: demo/pb-vertical + Normal Successful 30s KubeDB Ops-manager Operator Successfully resumed PgBouncer database: demo/pb-vertical + Normal Successful 30s KubeDB Ops-manager Operator Controller has Successfully scaled the PgBouncer database: demo/pb-vertical ``` Now, we are going to verify from the Pod yaml whether the resources of the pgbouncer has updated to meet up the desired state, Let's check, diff --git a/docs/images/day-2-operation/pgbouncer/vertical-scaling.svg b/docs/images/day-2-operation/pgbouncer/vertical-scaling.svg new file mode 100644 index 000000000..e8effa6bd --- /dev/null +++ b/docs/images/day-2-operation/pgbouncer/vertical-scaling.svg @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 0434c412bbad44414774afdcd1048a6a5aa7d815 Mon Sep 17 00:00:00 2001 From: Hiranmoy Das Chowdhury Date: Thu, 28 Nov 2024 12:55:10 +0600 Subject: [PATCH 14/29] autoscaling all done Signed-off-by: Hiranmoy Das Chowdhury --- .../compute/pgbouncer-autoscale.yaml | 23 ++++++++++++++++++ .../compute/pgbouncer-autoscaler.yaml | 21 ++++++++++++++++ .../autoscaler/compute/compute-autoscale.md | 4 --- .../pgbouncer/autoscaler/compute/overview.md | 2 +- .../day-2-operation/pgbouncer/autoscaling.png | Bin 0 -> 49475 bytes 5 files changed, 45 insertions(+), 5 deletions(-) create mode 100644 docs/examples/pgbouncer/autoscaling/compute/pgbouncer-autoscale.yaml create mode 100644 docs/examples/pgbouncer/autoscaling/compute/pgbouncer-autoscaler.yaml create mode 100644 docs/images/day-2-operation/pgbouncer/autoscaling.png diff --git a/docs/examples/pgbouncer/autoscaling/compute/pgbouncer-autoscale.yaml b/docs/examples/pgbouncer/autoscaling/compute/pgbouncer-autoscale.yaml new file mode 100644 index 000000000..d7cf7c1c1 --- /dev/null +++ b/docs/examples/pgbouncer/autoscaling/compute/pgbouncer-autoscale.yaml @@ -0,0 +1,23 @@ +apiVersion: kubedb.com/v1 +kind: PgBouncer +metadata: + name: pgbouncer-autoscale + namespace: demo +spec: + replicas: 1 + version: "1.18.0" + database: + syncUsers: true + databaseName: "postgres" + databaseRef: + name: "ha-postgres" + namespace: demo + connectionPool: + poolMode: session + port: 5432 + reservePoolSize: 5 + maxClientConnections: 87 + defaultPoolSize: 2 + minPoolSize: 1 + authType: md5 + deletionPolicy: WipeOut \ No newline at end of file diff --git a/docs/examples/pgbouncer/autoscaling/compute/pgbouncer-autoscaler.yaml b/docs/examples/pgbouncer/autoscaling/compute/pgbouncer-autoscaler.yaml new file mode 100644 index 000000000..37df8e5a7 --- /dev/null +++ b/docs/examples/pgbouncer/autoscaling/compute/pgbouncer-autoscaler.yaml @@ -0,0 +1,21 @@ +apiVersion: autoscaling.kubedb.com/v1alpha1 +kind: PgBouncerAutoscaler +metadata: + name: pgbouncer-autoscale-ops + namespace: demo +spec: + databaseRef: + name: pgbouncer-autoscale + compute: + pgbouncer: + trigger: "On" + podLifeTimeThreshold: 5m + resourceDiffPercentage: 20 + minAllowed: + cpu: 400m + memory: 400Mi + maxAllowed: + cpu: 1 + memory: 1Gi + controlledResources: ["cpu", "memory"] + containerControlledValues: "RequestsAndLimits" \ No newline at end of file diff --git a/docs/guides/pgbouncer/autoscaler/compute/compute-autoscale.md b/docs/guides/pgbouncer/autoscaler/compute/compute-autoscale.md index da194603e..a137dfa44 100644 --- a/docs/guides/pgbouncer/autoscaler/compute/compute-autoscale.md +++ b/docs/guides/pgbouncer/autoscaler/compute/compute-autoscale.md @@ -97,7 +97,6 @@ Let's check the Pod containers resources, $ kubectl get pod -n demo pgbouncer-autoscale-0 -o json | jq '.spec.containers[].resources' { "limits": { - "cpu": "200m", "memory": "300Mi" }, "requests": { @@ -112,7 +111,6 @@ Let's check the PgBouncer resources, $ kubectl get pgbouncer -n demo pgbouncer-autoscale -o json | jq '.spec.podTemplate.spec.containers[0].resources' { "limits": { - "cpu": "200m", "memory": "300Mi" }, "requests": { @@ -403,7 +401,6 @@ Now, we are going to verify from the Pod, and the PgBouncer yaml whether the res $ kubectl get pod -n demo pgbouncer-autoscale-0 -o json | jq '.spec.containers[].resources' { "limits": { - "cpu": "400m", "memory": "400Mi" }, "requests": { @@ -415,7 +412,6 @@ $ kubectl get pod -n demo pgbouncer-autoscale-0 -o json | jq '.spec.containers[] $ kubectl get pgbouncer -n demo pgbouncer-autoscale -o json | jq '.spec.podTemplate.spec.containers[0].resources' { "limits": { - "cpu": "400m", "memory": "400Mi" }, "requests": { diff --git a/docs/guides/pgbouncer/autoscaler/compute/overview.md b/docs/guides/pgbouncer/autoscaler/compute/overview.md index 87deb50da..ea2848cf8 100644 --- a/docs/guides/pgbouncer/autoscaler/compute/overview.md +++ b/docs/guides/pgbouncer/autoscaler/compute/overview.md @@ -28,7 +28,7 @@ This guide will give an overview on how KubeDB Autoscaler operator autoscales th The following diagram shows how KubeDB Autoscaler operator autoscales the resources of `PgBouncer`. Open the image in a new tab to see the enlarged version.
-  Compute Auto Scaling process of PgBouncer +  Compute Auto Scaling process of PgBouncer
Fig: Compute Auto Scaling process of PgBouncer
diff --git a/docs/images/day-2-operation/pgbouncer/autoscaling.png b/docs/images/day-2-operation/pgbouncer/autoscaling.png new file mode 100644 index 0000000000000000000000000000000000000000..f1b2ba2f1becc15d66242d08cafa7396b6222c17 GIT binary patch literal 49475 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 G2L2zK{AE}G literal 0 HcmV?d00001 From 93cb4ab7faede9edbdabd6599b59b6bd2ba5fcc9 Mon Sep 17 00:00:00 2001 From: Hiranmoy Das Chowdhury Date: Thu, 28 Nov 2024 16:41:07 +0600 Subject: [PATCH 15/29] all done for reconfiguration Signed-off-by: Hiranmoy Das Chowdhury --- .../reconfigure/pb-custom-config.yaml | 22 + .../reconfigure/pbops-reconfigure-apply.yaml | 17 + .../reconfigure/pbops-reconfigure.yaml | 15 + docs/guides/pgbouncer/reconfigure/overview.md | 2 +- .../reconfigure/reconfigure-pgbouncer.md | 584 ++++++------------ .../pgbouncer/reconfiguring.png | Bin 0 -> 43893 bytes 6 files changed, 243 insertions(+), 397 deletions(-) create mode 100644 docs/examples/pgbouncer/reconfigure/pb-custom-config.yaml create mode 100644 docs/examples/pgbouncer/reconfigure/pbops-reconfigure-apply.yaml create mode 100644 docs/examples/pgbouncer/reconfigure/pbops-reconfigure.yaml create mode 100644 docs/images/day-2-operation/pgbouncer/reconfiguring.png diff --git a/docs/examples/pgbouncer/reconfigure/pb-custom-config.yaml b/docs/examples/pgbouncer/reconfigure/pb-custom-config.yaml new file mode 100644 index 000000000..1851b3492 --- /dev/null +++ b/docs/examples/pgbouncer/reconfigure/pb-custom-config.yaml @@ -0,0 +1,22 @@ +apiVersion: kubedb.com/v1 +kind: PgBouncer +metadata: + name: pb-custom + namespace: demo +spec: + replicas: 1 + version: "1.18.0" + database: + syncUsers: true + databaseName: "postgres" + databaseRef: + name: "ha-postgres" + namespace: demo + connectionPool: + poolMode: session + port: 5432 + reservePoolSize: 5 + maxClientConnections: 87 + defaultPoolSize: 2 + minPoolSize: 1 + deletionPolicy: WipeOut \ No newline at end of file diff --git a/docs/examples/pgbouncer/reconfigure/pbops-reconfigure-apply.yaml b/docs/examples/pgbouncer/reconfigure/pbops-reconfigure-apply.yaml new file mode 100644 index 000000000..867d1a439 --- /dev/null +++ b/docs/examples/pgbouncer/reconfigure/pbops-reconfigure-apply.yaml @@ -0,0 +1,17 @@ +apiVersion: ops.kubedb.com/v1alpha1 +kind: PgBouncerOpsRequest +metadata: + name: pbops-reconfigure-apply + namespace: demo +spec: + type: Reconfigure + databaseRef: + name: pb-custom + configuration: + pgbouncer: + applyConfig: + pgbouncer.ini: |- + [pgbouncer] + auth_type=scram-sha-256 + timeout: 5m + apply: IfReady \ No newline at end of file diff --git a/docs/examples/pgbouncer/reconfigure/pbops-reconfigure.yaml b/docs/examples/pgbouncer/reconfigure/pbops-reconfigure.yaml new file mode 100644 index 000000000..705f385a0 --- /dev/null +++ b/docs/examples/pgbouncer/reconfigure/pbops-reconfigure.yaml @@ -0,0 +1,15 @@ +apiVersion: ops.kubedb.com/v1alpha1 +kind: PgBouncerOpsRequest +metadata: + name: pbops-reconfigure + namespace: demo +spec: + type: Reconfigure + databaseRef: + name: pb-custom + configuration: + pgbouncer: + configSecret: + name: new-custom-config + timeout: 5m + apply: IfReady \ No newline at end of file diff --git a/docs/guides/pgbouncer/reconfigure/overview.md b/docs/guides/pgbouncer/reconfigure/overview.md index 598acedb7..d926ecaeb 100644 --- a/docs/guides/pgbouncer/reconfigure/overview.md +++ b/docs/guides/pgbouncer/reconfigure/overview.md @@ -27,7 +27,7 @@ This guide will give an overview on how KubeDB Ops-manager operator reconfigures The following diagram shows how KubeDB Ops-manager operator reconfigures `PgBouncer`. Open the image in a new tab to see the enlarged version.
-  Reconfiguring process of PgBouncer +  Reconfiguring process of PgBouncer
Fig: Reconfiguring process of PgBouncer
diff --git a/docs/guides/pgbouncer/reconfigure/reconfigure-pgbouncer.md b/docs/guides/pgbouncer/reconfigure/reconfigure-pgbouncer.md index 1aeda10ab..4e0e3c4f5 100644 --- a/docs/guides/pgbouncer/reconfigure/reconfigure-pgbouncer.md +++ b/docs/guides/pgbouncer/reconfigure/reconfigure-pgbouncer.md @@ -110,43 +110,29 @@ Now, we will check if the pgbouncer has started with the custom configuration we Now, you can exec into the pgbouncer pod and find if the custom configuration is there, ```bash -$ kubectl exec -it -n demo pb-custom-0 -- bash -pb-custom-0:/$ cat opt/pgbouncer/etc/pgbouncer.ini -backend_hostname0 = 'ha-postgres.demo.svc' -backend_port0 = 5432 -backend_weight0 = 1 -backend_flag0 = 'ALWAYS_PRIMARY|DISALLOW_TO_FAILOVER' -backend_hostname1 = 'ha-postgres-standby.demo.svc' -backend_port1 = 5432 -backend_weight1 = 1 -backend_flag1 = 'DISALLOW_TO_FAILOVER' -max_pool = 60 -enable_pool_hba = on -listen_addresses = * -port = 9999 -socket_dir = '/var/run/pgbouncer' -pcp_listen_addresses = * -pcp_port = 9595 -pcp_socket_dir = '/var/run/pgbouncer' -log_per_node_statement = on -sr_check_period = 0 -health_check_period = 0 -backend_clustering_mode = 'streaming_replication' -num_init_children = 5 -child_life_time = 300 -child_max_connections = 0 -connection_life_time = 0 -client_idle_limit = 0 -connection_cache = on -load_balance_mode = on -ssl = 'off' -failover_on_backend_error = 'off' -log_min_messages = 'warning' -statement_level_load_balance = 'off' -memory_cache_enabled = 'off' -memqcache_oiddir = '/tmp/oiddir/' -allow_clear_text_frontend_auth = 'false' -failover_on_backend_error = 'off' +$ kubectl exec -it -n demo pb-custom-0 -- /bin/sh +pb-custom-0:/$ cat etc/config/pgbouncer.ini +[databases] +postgres= host=ha-postgres.demo.svc port=5432 dbname=postgres + +[pgbouncer] +max_client_conn = 87 +default_pool_size = 2 +min_pool_size = 1 +max_db_connections = 1 +logfile = /tmp/pgbouncer.log +listen_port = 5432 +ignore_startup_parameters = extra_float_digits +pidfile = /tmp/pgbouncer.pid +listen_addr = * +reserve_pool_size = 5 +reserve_pool_timeout = 5 +auth_type = scram-sha-256 +auth_file = /var/run/pgbouncer/secret/userlist +admin_users = pgbouncer +pool_mode = session +max_user_connections = 2 +stats_period = 60 pb-custom-0:/$ exit exit ``` @@ -231,68 +217,75 @@ Annotations: API Version: ops.kubedb.com/v1alpha1 Kind: PgBouncerOpsRequest Metadata: - Creation Timestamp: 2024-07-30T05:42:56Z + Creation Timestamp: 2024-11-28T10:06:23Z Generation: 1 - Resource Version: 95239 - UID: 54a12624-048c-49a6-b852-6286da587535 + Resource Version: 86377 + UID: f96d088e-a32b-40eb-bd9b-ca15a8370548 Spec: Apply: IfReady Configuration: - Config Secret: - Name: new-custom-config + Pgbouncer: + Config Secret: + Name: new-custom-config Database Ref: Name: pb-custom Timeout: 5m Type: Reconfigure Status: Conditions: - Last Transition Time: 2024-07-30T05:42:56Z - Message: PgBouncer ops-request has started to `Reconfigure` the PgBouncer nodes - Observed Generation: 1 - Reason: Reconfigure - Status: True - Type: Reconfigure - Last Transition Time: 2024-07-30T05:42:59Z - Message: Successfully paused database + Last Transition Time: 2024-11-28T10:06:23Z + Message: Controller has started to Progress with Reconfigure of PgBouncerOpsRequest: demo/pbops-reconfigure Observed Generation: 1 - Reason: DatabasePauseSucceeded + Reason: Running Status: True - Type: DatabasePauseSucceeded - Last Transition Time: 2024-07-30T05:43:00Z - Message: Successfully updated PetSet + Type: Running + Last Transition Time: 2024-11-28T10:06:26Z + Message: paused pgbouncer database Observed Generation: 1 - Reason: UpdatePetSets + Reason: Paused Status: True - Type: UpdatePetSets - Last Transition Time: 2024-07-30T05:43:00Z + Type: Paused + Last Transition Time: 2024-11-28T10:06:36Z Message: Successfully updated PgBouncer Observed Generation: 1 Reason: UpdateDatabase Status: True Type: UpdateDatabase - Last Transition Time: 2024-07-30T05:43:45Z - Message: Successfully Restarted Pods With Resources + Last Transition Time: 2024-11-28T10:06:36Z + Message: Successfully updated PgBouncer backend secret Observed Generation: 1 - Reason: RestartPods + Reason: UpdateBackendSecret Status: True - Type: RestartPods - Last Transition Time: 2024-07-30T05:43:05Z + Type: UpdateBackendSecret + Last Transition Time: 2024-11-28T10:06:41Z Message: get pod; ConditionStatus:True; PodName:pb-custom-0 Observed Generation: 1 Status: True Type: GetPod--pb-custom-0 - Last Transition Time: 2024-07-30T05:43:05Z - Message: evict pod; ConditionStatus:True; PodName:pb-custom-0 + Last Transition Time: 2024-11-28T10:07:16Z + Message: volume mount check; ConditionStatus:True; PodName:pb-custom-0 + Observed Generation: 1 + Status: True + Type: VolumeMountCheck--pb-custom-0 + Last Transition Time: 2024-11-28T10:07:21Z + Message: reload config; ConditionStatus:True; PodName:pb-custom-0 + Observed Generation: 1 + Status: True + Type: ReloadConfig--pb-custom-0 + Last Transition Time: 2024-11-28T10:07:21Z + Message: Reloading performed successfully in PgBouncer: demo/pb-custom for PgBouncerOpsRequest: pbops-reconfigure Observed Generation: 1 + Reason: ReloadPodsSucceeded Status: True - Type: EvictPod--pb-custom-0 - Last Transition Time: 2024-07-30T05:43:40Z - Message: check pod running; ConditionStatus:True; PodName:pb-custom-0 + Type: ReloadPods + Last Transition Time: 2024-11-28T10:07:21Z + Message: Successfully Reconfigured Observed Generation: 1 + Reason: Reconfigure Status: True - Type: CheckPodRunning--pb-custom-0 - Last Transition Time: 2024-07-30T05:43:45Z - Message: Successfully completed the reconfigure for PgBouncer + Type: Reconfigure + Last Transition Time: 2024-11-28T10:07:21Z + Message: Controller has successfully completed with Reconfigure of PgBouncerOpsRequest: demo/pbops-reconfigure Observed Generation: 1 Reason: Successful Status: True @@ -300,63 +293,55 @@ Status: Observed Generation: 1 Phase: Successful Events: - Type Reason Age From Message - ---- ------ ---- ---- ------- - Normal Starting 100s KubeDB Ops-manager Operator Start processing for PgBouncerOpsRequest: demo/pbops-reconfigure - Normal Starting 100s KubeDB Ops-manager Operator Pausing PgBouncer databse: demo/pb-custom - Normal Successful 100s KubeDB Ops-manager Operator Successfully paused PgBouncer database: demo/pb-custom for PgBouncerOpsRequest: pbops-reconfigure - Normal UpdatePetSets 96s KubeDB Ops-manager Operator Successfully updated PetSet - Normal UpdatePetSets 96s KubeDB Ops-manager Operator Successfully updated PetSet - Normal UpdateDatabase 96s KubeDB Ops-manager Operator Successfully updated PgBouncer - Warning get pod; ConditionStatus:True; PodName:pb-custom-0 91s KubeDB Ops-manager Operator get pod; ConditionStatus:True; PodName:pb-custom-0 - Warning evict pod; ConditionStatus:True; PodName:pb-custom-0 91s KubeDB Ops-manager Operator evict pod; ConditionStatus:True; PodName:pb-custom-0 - Warning check pod running; ConditionStatus:False; PodName:pb-custom-0 86s KubeDB Ops-manager Operator check pod running; ConditionStatus:False; PodName:pb-custom-0 - Warning check pod running; ConditionStatus:True; PodName:pb-custom-0 56s KubeDB Ops-manager Operator check pod running; ConditionStatus:True; PodName:pb-custom-0 - Normal RestartPods 51s KubeDB Ops-manager Operator Successfully Restarted Pods With Resources - Normal Starting 51s KubeDB Ops-manager Operator Resuming PgBouncer database: demo/pb-custom - Normal Successful 51s KubeDB Ops-manager Operator Successfully resumed PgBouncer database: demo/pb-custom for PgBouncerOpsRequest: pbops-reconfigure + Type Reason Age From Message + ---- ------ ---- ---- ------- + Normal Starting 70s KubeDB Ops-manager Operator Start processing for PgBouncerOpsRequest: demo/pbops-reconfigure + Normal Starting 70s KubeDB Ops-manager Operator Pausing PgBouncer databse: demo/pb-custom + Normal Successful 70s KubeDB Ops-manager Operator Successfully paused PgBouncer database: demo/pb-custom for PgBouncerOpsRequest: pbops-reconfigure + Warning get pod; ConditionStatus:True; PodName:pb-custom-0 52s KubeDB Ops-manager Operator get pod; ConditionStatus:True; PodName:pb-custom-0 + Warning volume mount check; ConditionStatus:False; PodName:pb-custom-0 52s KubeDB Ops-manager Operator volume mount check; ConditionStatus:False; PodName:pb-custom-0 + Warning get pod; ConditionStatus:True; PodName:pb-custom-0 47s KubeDB Ops-manager Operator get pod; ConditionStatus:True; PodName:pb-custom-0 + Warning get pod; ConditionStatus:True; PodName:pb-custom-0 42s KubeDB Ops-manager Operator get pod; ConditionStatus:True; PodName:pb-custom-0 + Warning get pod; ConditionStatus:True; PodName:pb-custom-0 37s KubeDB Ops-manager Operator get pod; ConditionStatus:True; PodName:pb-custom-0 + Warning get pod; ConditionStatus:True; PodName:pb-custom-0 32s KubeDB Ops-manager Operator get pod; ConditionStatus:True; PodName:pb-custom-0 + Warning get pod; ConditionStatus:True; PodName:pb-custom-0 27s KubeDB Ops-manager Operator get pod; ConditionStatus:True; PodName:pb-custom-0 + Warning get pod; ConditionStatus:True; PodName:pb-custom-0 22s KubeDB Ops-manager Operator get pod; ConditionStatus:True; PodName:pb-custom-0 + Warning get pod; ConditionStatus:True; PodName:pb-custom-0 17s KubeDB Ops-manager Operator get pod; ConditionStatus:True; PodName:pb-custom-0 + Warning volume mount check; ConditionStatus:True; PodName:pb-custom-0 17s KubeDB Ops-manager Operator volume mount check; ConditionStatus:True; PodName:pb-custom-0 + Warning reload config; ConditionStatus:True; PodName:pb-custom-0 12s KubeDB Ops-manager Operator reload config; ConditionStatus:True; PodName:pb-custom-0 + Warning reload config; ConditionStatus:True; PodName:pb-custom-0 12s KubeDB Ops-manager Operator reload config; ConditionStatus:True; PodName:pb-custom-0 + Normal Successful 12s KubeDB Ops-manager Operator Reloading performed successfully in PgBouncer: demo/pb-custom for PgBouncerOpsRequest: pbops-reconfigure + Normal Starting 12s KubeDB Ops-manager Operator Resuming PgBouncer database: demo/pb-custom + Normal Successful 12s KubeDB Ops-manager Operator Successfully resumed PgBouncer database: demo/pb-custom + Normal Successful 12s KubeDB Ops-manager Operator Controller has Successfully Reconfigured PgBouncer databases: demo/pb-custom ``` Now let's exec into the pgbouncer pod and check the new configuration we have provided. ```bash -$ kubectl exec -it -n demo pb-custom-0 -- bash -pb-custom-0:/$ cat opt/pgbouncer/etc/pgbouncer.ini -backend_hostname0 = 'ha-postgres.demo.svc' -backend_port0 = 5432 -backend_weight0 = 1 -backend_flag0 = 'ALWAYS_PRIMARY|DISALLOW_TO_FAILOVER' -backend_hostname1 = 'ha-postgres-standby.demo.svc' -backend_port1 = 5432 -backend_weight1 = 1 -backend_flag1 = 'DISALLOW_TO_FAILOVER' -listen_addresses = * -log_per_node_statement = on -num_init_children = 5 -max_pool = 50 -child_life_time = '300' -child_max_connections = 0 -connection_life_time = 0 -client_idle_limit = 0 -connection_cache = on -load_balance_mode = on -log_min_messages = 'warning' -statement_level_load_balance = 'off' -memory_cache_enabled = 'off' -enable_pool_hba = on -port = 9999 -socket_dir = '/var/run/pgbouncer' -pcp_listen_addresses = * -pcp_port = 9595 -pcp_socket_dir = '/var/run/pgbouncer' -sr_check_period = 0 -health_check_period = 0 -backend_clustering_mode = 'streaming_replication' -ssl = 'off' -failover_on_backend_error = 'off' -memqcache_oiddir = '/tmp/oiddir/' -allow_clear_text_frontend_auth = 'false' -failover_on_backend_error = 'off' +$ kubectl exec -it -n demo pb-custom-0 -- /bin/sh +pb-custom-0:/$ cat etc/config/pgbouncer.ini +[databases] +postgres= host=ha-postgres.demo.svc port=5432 dbname=postgres + +[pgbouncer] +max_db_connections = 1 +logfile = /tmp/pgbouncer.log +listen_addr = * +admin_users = pgbouncer +pool_mode = session +max_client_conn = 87 +listen_port = 5432 +ignore_startup_parameters = extra_float_digits +auth_file = /var/run/pgbouncer/secret/userlist +default_pool_size = 2 +min_pool_size = 1 +max_user_connections = 2 +stats_period = 60 +auth_type = md5 +pidfile = /tmp/pgbouncer.pid +reserve_pool_size = 5 +reserve_pool_timeout = 5 pb-custom-0:/$ exit exit ``` @@ -430,68 +415,76 @@ Annotations: API Version: ops.kubedb.com/v1alpha1 Kind: PgBouncerOpsRequest Metadata: - Creation Timestamp: 2024-07-30T05:51:18Z + Creation Timestamp: 2024-11-28T10:11:52Z Generation: 1 - Resource Version: 95874 - UID: 92b0f18c-a329-4bb7-85d0-ef66f32bf57a + Resource Version: 86774 + UID: a4b8e8b5-0b82-4391-a8fe-66911aa5bee6 Spec: Apply: IfReady Configuration: - Apply Config: - pgbouncer.ini: max_pool = 75 + Pgbouncer: + Apply Config: + pgbouncer.ini: [pgbouncer] +auth_type=scram-sha-256 Database Ref: Name: pb-custom Timeout: 5m Type: Reconfigure Status: Conditions: - Last Transition Time: 2024-07-30T05:51:18Z - Message: PgBouncer ops-request has started to `Reconfigure` the PgBouncer nodes - Observed Generation: 1 - Reason: Reconfigure - Status: True - Type: Reconfigure - Last Transition Time: 2024-07-30T05:51:21Z - Message: Successfully paused database + Last Transition Time: 2024-11-28T10:11:52Z + Message: Controller has started to Progress with Reconfigure of PgBouncerOpsRequest: demo/pbops-reconfigure-apply Observed Generation: 1 - Reason: DatabasePauseSucceeded + Reason: Running Status: True - Type: DatabasePauseSucceeded - Last Transition Time: 2024-07-30T05:51:21Z - Message: Successfully updated PetSet + Type: Running + Last Transition Time: 2024-11-28T10:11:55Z + Message: paused pgbouncer database Observed Generation: 1 - Reason: UpdatePetSets + Reason: Paused Status: True - Type: UpdatePetSets - Last Transition Time: 2024-07-30T05:51:22Z + Type: Paused + Last Transition Time: 2024-11-28T10:11:55Z Message: Successfully updated PgBouncer Observed Generation: 1 Reason: UpdateDatabase Status: True Type: UpdateDatabase - Last Transition Time: 2024-07-30T05:52:07Z - Message: Successfully Restarted Pods With Resources + Last Transition Time: 2024-11-28T10:11:55Z + Message: Successfully updated PgBouncer backend secret Observed Generation: 1 - Reason: RestartPods + Reason: UpdateBackendSecret Status: True - Type: RestartPods - Last Transition Time: 2024-07-30T05:51:27Z + Type: UpdateBackendSecret + Last Transition Time: 2024-11-28T10:12:00Z Message: get pod; ConditionStatus:True; PodName:pb-custom-0 Observed Generation: 1 Status: True Type: GetPod--pb-custom-0 - Last Transition Time: 2024-07-30T05:51:27Z - Message: evict pod; ConditionStatus:True; PodName:pb-custom-0 + Last Transition Time: 2024-11-28T10:12:00Z + Message: volume mount check; ConditionStatus:True; PodName:pb-custom-0 + Observed Generation: 1 + Status: True + Type: VolumeMountCheck--pb-custom-0 + Last Transition Time: 2024-11-28T10:12:05Z + Message: reload config; ConditionStatus:True; PodName:pb-custom-0 Observed Generation: 1 Status: True - Type: EvictPod--pb-custom-0 - Last Transition Time: 2024-07-30T05:52:02Z - Message: check pod running; ConditionStatus:True; PodName:pb-custom-0 + Type: ReloadConfig--pb-custom-0 + Last Transition Time: 2024-11-28T10:12:05Z + Message: Reloading performed successfully in PgBouncer: demo/pb-custom for PgBouncerOpsRequest: pbops-reconfigure-apply Observed Generation: 1 + Reason: ReloadPodsSucceeded Status: True - Type: CheckPodRunning--pb-custom-0 - Last Transition Time: 2024-07-30T05:52:07Z - Message: Successfully completed the reconfigure for PgBouncer + Type: ReloadPods + Last Transition Time: 2024-11-28T10:12:05Z + Message: Successfully Reconfigured + Observed Generation: 1 + Reason: Reconfigure + Status: True + Type: Reconfigure + Last Transition Time: 2024-11-28T10:12:05Z + Message: Controller has successfully completed with Reconfigure of PgBouncerOpsRequest: demo/pbops-reconfigure-apply Observed Generation: 1 Reason: Successful Status: True @@ -501,263 +494,62 @@ Status: Events: Type Reason Age From Message ---- ------ ---- ---- ------- - Normal Starting 77s KubeDB Ops-manager Operator Start processing for PgBouncerOpsRequest: demo/pbops-reconfigure-apply - Normal Starting 77s KubeDB Ops-manager Operator Pausing PgBouncer databse: demo/pb-custom - Normal Successful 77s KubeDB Ops-manager Operator Successfully paused PgBouncer database: demo/pb-custom for PgBouncerOpsRequest: pbops-reconfigure-apply - Normal UpdatePetSets 74s KubeDB Ops-manager Operator Successfully updated PetSet - Normal UpdatePetSets 73s KubeDB Ops-manager Operator Successfully updated PetSet - Normal UpdateDatabase 73s KubeDB Ops-manager Operator Successfully updated PgBouncer - Warning get pod; ConditionStatus:True; PodName:pb-custom-0 68s KubeDB Ops-manager Operator get pod; ConditionStatus:True; PodName:pb-custom-0 - Warning evict pod; ConditionStatus:True; PodName:pb-custom-0 68s KubeDB Ops-manager Operator evict pod; ConditionStatus:True; PodName:pb-custom-0 - Warning check pod running; ConditionStatus:False; PodName:pb-custom-0 63s KubeDB Ops-manager Operator check pod running; ConditionStatus:False; PodName:pb-custom-0 - Warning check pod running; ConditionStatus:True; PodName:pb-custom-0 33s KubeDB Ops-manager Operator check pod running; ConditionStatus:True; PodName:pb-custom-0 - Normal RestartPods 28s KubeDB Ops-manager Operator Successfully Restarted Pods With Resources - Normal Starting 28s KubeDB Ops-manager Operator Resuming PgBouncer database: demo/pb-custom - Normal Successful 28s KubeDB Ops-manager Operator Successfully resumed PgBouncer database: demo/pb-custom for PgBouncerOpsRequest: pbops-reconfigure-apply + Normal Starting 54s KubeDB Ops-manager Operator Start processing for PgBouncerOpsRequest: demo/pbops-reconfigure-apply + Normal Starting 54s KubeDB Ops-manager Operator Pausing PgBouncer databse: demo/pb-custom + Normal Successful 54s KubeDB Ops-manager Operator Successfully paused PgBouncer database: demo/pb-custom for PgBouncerOpsRequest: pbops-reconfigure-apply + Warning get pod; ConditionStatus:True; PodName:pb-custom-0 46s KubeDB Ops-manager Operator get pod; ConditionStatus:True; PodName:pb-custom-0 + Warning volume mount check; ConditionStatus:True; PodName:pb-custom-0 46s KubeDB Ops-manager Operator volume mount check; ConditionStatus:True; PodName:pb-custom-0 + Warning reload config; ConditionStatus:True; PodName:pb-custom-0 41s KubeDB Ops-manager Operator reload config; ConditionStatus:True; PodName:pb-custom-0 + Warning reload config; ConditionStatus:True; PodName:pb-custom-0 41s KubeDB Ops-manager Operator reload config; ConditionStatus:True; PodName:pb-custom-0 + Normal Successful 41s KubeDB Ops-manager Operator Reloading performed successfully in PgBouncer: demo/pb-custom for PgBouncerOpsRequest: pbops-reconfigure-apply + Normal Starting 41s KubeDB Ops-manager Operator Resuming PgBouncer database: demo/pb-custom + Normal Successful 41s KubeDB Ops-manager Operator Successfully resumed PgBouncer database: demo/pb-custom + Normal Successful 41s KubeDB Ops-manager Operator Controller has Successfully Reconfigured PgBouncer databases: demo/pb-custom + Normal Starting 41s KubeDB Ops-manager Operator Resuming PgBouncer database: demo/pb-custom + Normal Successful 41s KubeDB Ops-manager Operator Successfully resumed PgBouncer database: demo/pb-custom + Normal Successful 41s KubeDB Ops-manager Operator Controller has Successfully Reconfigured PgBouncer databases: demo/pb-custom ``` Now let's exec into the pgbouncer pod and check the new configuration we have provided. ```bash -$ kubectl exec -it -n demo pb-custom-0 -- bash -pb-custom-0:/$ cat opt/pgbouncer/etc/pgbouncer.ini -memory_cache_enabled = 'off' -num_init_children = 5 -pcp_socket_dir = '/var/run/pgbouncer' -port = '9999' -enable_pool_hba = on -log_min_messages = 'warning' -pcp_port = '9595' -sr_check_period = 0 -ssl = 'off' -backend_weight1 = 1 -load_balance_mode = on -backend_weight0 = 1 -backend_port0 = '5432' -connection_cache = on -backend_hostname1 = 'ha-postgres-standby.demo.svc' -health_check_period = 0 -memqcache_oiddir = '/tmp/oiddir/' -statement_level_load_balance = 'off' -allow_clear_text_frontend_auth = 'false' -log_per_node_statement = on -backend_hostname0 = 'ha-postgres.demo.svc' -backend_flag1 = 'DISALLOW_TO_FAILOVER' -listen_addresses = * -failover_on_backend_error = 'off' -pcp_listen_addresses = * -child_max_connections = 0 -socket_dir = '/var/run/pgbouncer' -backend_flag0 = 'ALWAYS_PRIMARY|DISALLOW_TO_FAILOVER' -backend_port1 = '5432' -backend_clustering_mode = 'streaming_replication' -connection_life_time = 0 -child_life_time = '300' -max_pool = 75 -client_idle_limit = 0 -failover_on_backend_error = 'off' +$ kubectl exec -it -n demo pb-custom-0 -- /bin/sh +pb-custom-0:/$ cat etc/config/pgbouncer.ini +[databases] +postgres= host=ha-postgres.demo.svc port=5432 dbname=postgres + +[pgbouncer] +stats_period = 60 +pidfile = /tmp/pgbouncer.pid +pool_mode = session +reserve_pool_timeout = 5 +max_client_conn = 87 +min_pool_size = 1 +default_pool_size = 2 +listen_addr = * +max_db_connections = 1 +max_user_connections = 2 +auth_type=scram-sha-256 +ignore_startup_parameters = extra_float_digits +admin_users = pgbouncer +auth_file = /var/run/pgbouncer/secret/userlist +logfile = /tmp/pgbouncer.log +listen_port = 5432 +reserve_pool_size = 5 pb-custom-0:/$ exit exit ``` As we can see from the configuration of running pgbouncer, the value of `auth_type` has been changed from `md5` to `scram-sha-256`. So the reconfiguration of the pgbouncer using the `applyConfig` field is successful. - ### Remove config -Now we will reconfigure this pgbouncer to remove the custom config provided and get it back to the default config. We will use the `removeCustomConfig` field of the `PgBouncerOpsRequest`. This will remove all the custom config provided and get the pgbouncer back to the default config. - -#### Create PgBouncerOpsRequest - -Now, we will use the `removeCustomConfig` field in the `PgBouncerOpsRequest` CR. The `PgBouncerOpsRequest` yaml is given below, - -```yaml -apiVersion: ops.kubedb.com/v1alpha1 -kind: PgBouncerOpsRequest -metadata: - name: pbops-reconfigure-remove - namespace: demo -spec: - type: Reconfigure - databaseRef: - name: pb-custom - configuration: - pgbouncer: - removeCustomConfig: true - timeout: 5m - apply: IfReady -``` - -Here, +This will remove all the custom config previously provided. After this Ops-manager operator will merge the new given config with the default config and apply this. - `spec.databaseRef.name` specifies that we are reconfiguring `pb-custom` pgbouncer. - `spec.type` specifies that we are performing `Reconfigure` on our pgbouncer. -- `spec.configuration.pgbouncer.removeCustomConfig` specifies for boolean values to remove custom configuration. - -Let's create the `PgBouncerOpsRequest` CR we have shown above, - -```bash -$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/pgbouncer/reconfigure/pbops-reconfigure-remove.yaml -pgbounceropsrequest.ops.kubedb.com/pbops-reconfigure-remove created -``` - -#### Verify if the configuration is removed - -If everything goes well, `KubeDB` Ops-manager operator will remove the custom configuration and move back to the default configuration. - -Let's wait for `PgBouncerOpsRequest` to be `Successful`. Run the following command to watch `PgBouncerOpsRequest` CR, - -```bash -$ watch kubectl get pgbounceropsrequest -n demo -Every 2.0s: kubectl get pgbounceropsrequest -n demo -kubectl get pgbounceropsrequest -n demo -NAME TYPE STATUS AGE -pbops-reconfigure Reconfigure Successful 71m -pbops-reconfigure-apply Reconfigure Successful 63m -pbops-reconfigure-remove Reconfigure Successful 57s -``` - -We can see from the above output that the `PgBouncerOpsRequest` has succeeded. If we describe the `PgBouncerOpsRequest` we will get an overview of the steps that were followed to reconfigure the pgbouncer. - -```bash -$ kubectl describe pgbounceropsrequest -n demo pbops-reconfigure-remove -Name: pbops-reconfigure-remove -Namespace: demo -Labels: -Annotations: -API Version: ops.kubedb.com/v1alpha1 -Kind: PgBouncerOpsRequest -Metadata: - Creation Timestamp: 2024-07-30T06:53:27Z - Generation: 1 - Resource Version: 99827 - UID: 24c9cba3-5e85-40dc-96f3-373d2dd7a8ba -Spec: - Apply: IfReady - Configuration: - Remove Custom Config: true - Database Ref: - Name: pb-custom - Timeout: 5m - Type: Reconfigure -Status: - Conditions: - Last Transition Time: 2024-07-30T06:53:28Z - Message: PgBouncer ops-request has started to `Reconfigure` the PgBouncer nodes - Observed Generation: 1 - Reason: Reconfigure - Status: True - Type: Reconfigure - Last Transition Time: 2024-07-30T06:53:31Z - Message: Successfully paused database - Observed Generation: 1 - Reason: DatabasePauseSucceeded - Status: True - Type: DatabasePauseSucceeded - Last Transition Time: 2024-07-30T06:53:31Z - Message: Successfully updated PetSet - Observed Generation: 1 - Reason: UpdatePetSets - Status: True - Type: UpdatePetSets - Last Transition Time: 2024-07-30T06:53:32Z - Message: Successfully updated PgBouncer - Observed Generation: 1 - Reason: UpdateDatabase - Status: True - Type: UpdateDatabase - Last Transition Time: 2024-07-30T06:54:17Z - Message: Successfully Restarted Pods With Resources - Observed Generation: 1 - Reason: RestartPods - Status: True - Type: RestartPods - Last Transition Time: 2024-07-30T06:53:37Z - Message: get pod; ConditionStatus:True; PodName:pb-custom-0 - Observed Generation: 1 - Status: True - Type: GetPod--pb-custom-0 - Last Transition Time: 2024-07-30T06:53:37Z - Message: evict pod; ConditionStatus:True; PodName:pb-custom-0 - Observed Generation: 1 - Status: True - Type: EvictPod--pb-custom-0 - Last Transition Time: 2024-07-30T06:54:12Z - Message: check pod running; ConditionStatus:True; PodName:pb-custom-0 - Observed Generation: 1 - Status: True - Type: CheckPodRunning--pb-custom-0 - Last Transition Time: 2024-07-30T06:54:17Z - Message: Successfully completed the reconfigure for PgBouncer - Observed Generation: 1 - Reason: Successful - Status: True - Type: Successful - Observed Generation: 1 - Phase: Successful -Events: - Type Reason Age From Message - ---- ------ ---- ---- ------- - Normal Starting 74s KubeDB Ops-manager Operator Start processing for PgBouncerOpsRequest: demo/pbops-reconfigure-remove - Normal Starting 74s KubeDB Ops-manager Operator Pausing PgBouncer databse: demo/pb-custom - Normal Successful 74s KubeDB Ops-manager Operator Successfully paused PgBouncer database: demo/pb-custom for PgBouncerOpsRequest: pbops-reconfigure-remove - Normal UpdatePetSets 71s KubeDB Ops-manager Operator Successfully updated PetSet - Normal UpdatePetSets 70s KubeDB Ops-manager Operator Successfully updated PetSet - Normal UpdateDatabase 70s KubeDB Ops-manager Operator Successfully updated PgBouncer - Warning get pod; ConditionStatus:True; PodName:pb-custom-0 65s KubeDB Ops-manager Operator get pod; ConditionStatus:True; PodName:pb-custom-0 - Warning evict pod; ConditionStatus:True; PodName:pb-custom-0 65s KubeDB Ops-manager Operator evict pod; ConditionStatus:True; PodName:pb-custom-0 - Warning check pod running; ConditionStatus:False; PodName:pb-custom-0 60s KubeDB Ops-manager Operator check pod running; ConditionStatus:False; PodName:pb-custom-0 - Warning check pod running; ConditionStatus:True; PodName:pb-custom-0 30s KubeDB Ops-manager Operator check pod running; ConditionStatus:True; PodName:pb-custom-0 - Normal RestartPods 25s KubeDB Ops-manager Operator Successfully Restarted Pods With Resources - Normal Starting 25s KubeDB Ops-manager Operator Resuming PgBouncer database: demo/pb-custom - Normal Successful 25s KubeDB Ops-manager Operator Successfully resumed PgBouncer database: demo/pb-custom for PgBouncerOpsRequest: pbops-reconfigure-remove -``` - -Now let's exec into the pgbouncer pod and check the configuration. - -```bash -$ kubectl exec -it -n demo pb-custom-0 -- bash -pb-custom-0:/$ cat opt/pgbouncer/etc/pgbouncer.ini -backend_hostname0 = 'ha-postgres.demo.svc' -backend_port0 = 5432 -backend_weight0 = 1 -backend_flag0 = 'ALWAYS_PRIMARY|DISALLOW_TO_FAILOVER' -backend_hostname1 = 'ha-postgres-standby.demo.svc' -backend_port1 = 5432 -backend_weight1 = 1 -backend_flag1 = 'DISALLOW_TO_FAILOVER' -enable_pool_hba = on -listen_addresses = * -port = 9999 -socket_dir = '/var/run/pgbouncer' -pcp_listen_addresses = * -pcp_port = 9595 -pcp_socket_dir = '/var/run/pgbouncer' -log_per_node_statement = on -sr_check_period = 0 -health_check_period = 0 -backend_clustering_mode = 'streaming_replication' -num_init_children = 5 -max_pool = 15 -child_life_time = 300 -child_max_connections = 0 -connection_life_time = 0 -client_idle_limit = 0 -connection_cache = on -load_balance_mode = on -ssl = 'off' -failover_on_backend_error = 'off' -log_min_messages = 'warning' -statement_level_load_balance = 'off' -memory_cache_enabled = 'off' -memqcache_oiddir = '/tmp/oiddir/' -allow_clear_text_frontend_auth = 'false' -failover_on_backend_error = 'off' -pb-custom-0:/$ exit -exit -``` +- `spec.configuration.pgbouncer.removeCustomConfig` specifies for boolean values to remove previous custom configuration. -As we can see from the configuration of running pgbouncer, the value of `auth_type` has been changed from `scram-sha-256` to `md5`. So the reconfiguration of the pgbouncer using the `removeCustomConfig` field is successful. ## Cleaning Up @@ -765,7 +557,7 @@ As we can see from the configuration of running pgbouncer, the value of `auth_ty To clean up the Kubernetes resources created by this tutorial, run: ```bash kubectl delete -n demo pb/pb-custom -kubectl delete pgbounceropsrequest -n demo pbops-reconfigure pbops-reconfigure-apply pbops-reconfigure-remove +kubectl delete pgbounceropsrequest -n demo pbops-reconfigure pbops-reconfigure-apply kubectl delete pg -n demo ha-postgres kubectl delete ns demo ``` \ No newline at end of file diff --git a/docs/images/day-2-operation/pgbouncer/reconfiguring.png b/docs/images/day-2-operation/pgbouncer/reconfiguring.png new file mode 100644 index 0000000000000000000000000000000000000000..11c617da0c94f81a1badec0aefe3eec877787069 GIT binary patch literal 43893 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
?}@>p7uoWO%vtxPHX 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 Date: Thu, 28 Nov 2024 17:01:14 +0600 Subject: [PATCH 16/29] restart done Signed-off-by: Hiranmoy Das Chowdhury --- docs/examples/pgbouncer/restart/ops.yaml | 11 +++++++++ .../examples/pgbouncer/restart/pgbouncer.yaml | 23 +++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 docs/examples/pgbouncer/restart/ops.yaml create mode 100644 docs/examples/pgbouncer/restart/pgbouncer.yaml diff --git a/docs/examples/pgbouncer/restart/ops.yaml b/docs/examples/pgbouncer/restart/ops.yaml new file mode 100644 index 000000000..cff7b4901 --- /dev/null +++ b/docs/examples/pgbouncer/restart/ops.yaml @@ -0,0 +1,11 @@ +apiVersion: ops.kubedb.com/v1alpha1 +kind: PgBouncerOpsRequest +metadata: + name: restart-pgbouncer + namespace: demo +spec: + type: Restart + databaseRef: + name: pgbouncer + timeout: 3m + apply: Always \ No newline at end of file diff --git a/docs/examples/pgbouncer/restart/pgbouncer.yaml b/docs/examples/pgbouncer/restart/pgbouncer.yaml new file mode 100644 index 000000000..30d3b3cfc --- /dev/null +++ b/docs/examples/pgbouncer/restart/pgbouncer.yaml @@ -0,0 +1,23 @@ +apiVersion: kubedb.com/v1 +kind: PgBouncer +metadata: + name: pgbouncer + namespace: demo +spec: + replicas: 1 + version: "1.18.0" + database: + syncUsers: true + databaseName: "postgres" + databaseRef: + name: "ha-postgres" + namespace: demo + connectionPool: + poolMode: session + port: 5432 + reservePoolSize: 5 + maxClientConnections: 87 + defaultPoolSize: 2 + minPoolSize: 1 + authType: md5 + deletionPolicy: WipeOut \ No newline at end of file From 941fe5de757eed9900a1d66ccafeda433f56d7df Mon Sep 17 00:00:00 2001 From: Hiranmoy Das Chowdhury Date: Wed, 11 Dec 2024 17:44:24 +0600 Subject: [PATCH 17/29] duplication removed Signed-off-by: Hiranmoy Das Chowdhury --- .../guides/pgbouncer/quickstart/quickstart.md | 27 ------------------- 1 file changed, 27 deletions(-) diff --git a/docs/guides/pgbouncer/quickstart/quickstart.md b/docs/guides/pgbouncer/quickstart/quickstart.md index 5b5dcfcc7..447cc7fd6 100644 --- a/docs/guides/pgbouncer/quickstart/quickstart.md +++ b/docs/guides/pgbouncer/quickstart/quickstart.md @@ -186,33 +186,6 @@ $ kubectl create -f https://github.com/kubedb/docs/raw/{{< param "info.version" pgbouncer.kubedb.com/pgbouncer-server created ``` -```yaml -apiVersion: kubedb.com/v1 -kind: PgBouncer -metadata: - name: pgbouncer-server - namespace: demo -spec: - version: "1.18.0" - replicas: 1 - database: - syncUsers: true - databaseName: "postgres" - databaseRef: - name: "quick-postgres" - namespace: demo - connectionPool: - port: 5432 - maxClientConnections: 20 - reservePoolSize: 5 - terminationPolicy: Delete -``` - -```bash -$ kubectl create -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/pgbouncer/quickstart/pgbouncer-server-v1.yaml -pgbouncer.kubedb.com/pgbouncer-server created -``` - Here, - `spec.version` is name of the PgBouncerVersion crd where the docker images are specified. In this tutorial, a PgBouncer with base image version 1.17.0 is created. From cafaaf93805749220b1633788e93e89103260491 Mon Sep 17 00:00:00 2001 From: Hiranmoy Das Chowdhury Date: Wed, 11 Dec 2024 17:47:46 +0600 Subject: [PATCH 18/29] build fixed Signed-off-by: Hiranmoy Das Chowdhury --- .../guides/pgbouncer/quickstart/quickstart.md | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/docs/guides/pgbouncer/quickstart/quickstart.md b/docs/guides/pgbouncer/quickstart/quickstart.md index 447cc7fd6..187e9ad53 100644 --- a/docs/guides/pgbouncer/quickstart/quickstart.md +++ b/docs/guides/pgbouncer/quickstart/quickstart.md @@ -186,6 +186,33 @@ $ kubectl create -f https://github.com/kubedb/docs/raw/{{< param "info.version" pgbouncer.kubedb.com/pgbouncer-server created ``` +```yaml +apiVersion: kubedb.com/v1alpha2 +kind: PgBouncer +metadata: + name: pgbouncer-server + namespace: demo +spec: + version: "1.18.0" + replicas: 1 + database: + syncUsers: true + databaseName: "postgres" + databaseRef: + name: "quick-postgres" + namespace: demo + connectionPool: + port: 5432 + maxClientConnections: 20 + reservePoolSize: 5 + terminationPolicy: Delete +``` + +```bash +$ kubectl create -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/pgbouncer/quickstart/pgbouncer-server-v1.yaml +pgbouncer.kubedb.com/pgbouncer-server created +``` + Here, - `spec.version` is name of the PgBouncerVersion crd where the docker images are specified. In this tutorial, a PgBouncer with base image version 1.17.0 is created. From a35e5c92c509dbafc84b4bbac936e46f0326da1b Mon Sep 17 00:00:00 2001 From: Hiranmoy Das Chowdhury Date: Wed, 11 Dec 2024 18:10:22 +0600 Subject: [PATCH 19/29] remove reconfigure tls Signed-off-by: Hiranmoy Das Chowdhury --- docs/guides/pgbouncer/concepts/opsrequest.md | 77 ++------------------ 1 file changed, 5 insertions(+), 72 deletions(-) diff --git a/docs/guides/pgbouncer/concepts/opsrequest.md b/docs/guides/pgbouncer/concepts/opsrequest.md index 45ba3aaa9..3a209c66d 100644 --- a/docs/guides/pgbouncer/concepts/opsrequest.md +++ b/docs/guides/pgbouncer/concepts/opsrequest.md @@ -109,7 +109,8 @@ spec: databaseRef: name: pgbouncer-server configuration: - removeCustomConfig: true + pgbouncer: + removeCustomConfig: true ``` ```yaml @@ -123,65 +124,9 @@ spec: databaseRef: name: pgbouncer-server configuration: - configSecret: - name: new-custom-config -``` - - -**Sample `PgBouncerOpsRequest` Objects for Reconfiguring TLS:** - -```yaml -apiVersion: ops.kubedb.com/v1alpha1 -kind: PgBouncerOpsRequest -metadata: - name: tls - namespace: demo -spec: - type: ReconfigureTLS - databaseRef: - name: pgbouncer-server - tls: - sslMode: verify-ca - clientAuthMode: cert - issuerRef: - name: pgbouncer-ca-issuer - kind: Issuer - apiGroup: "cert-manager.io" - certificates: - - alias: client - subject: - organizations: - - kubedb - organizationalUnits: - - client -``` - -```yaml -apiVersion: ops.kubedb.com/v1alpha1 -kind: PgBouncerOpsRequest -metadata: - name: tls - namespace: demo -spec: - type: ReconfigureTLS - databaseRef: - name: pgbouncer-server - tls: - rotateCertificates: true -``` - -```yaml -apiVersion: ops.kubedb.com/v1alpha1 -kind: PgBouncerOpsRequest -metadata: - name: tls - namespace: demo -spec: - type: ReconfigureTLS - databaseRef: - name: pgbouncer-server - tls: - remove: true + pgbouncer: + configSecret: + name: new-custom-config ``` Here, we are going to describe the various sections of a `PgBouncerOpsRequest` crd. @@ -202,7 +147,6 @@ A `PgBouncerOpsRequest` object has the following fields in the `spec` section. - `HorizontalScaling` - `VerticalScaling` - `Reconfigure` -- `ReconfigureTLS` - `Restart` > You can perform only one type of operation on a single `PgBouncerOpsRequest` CR. For example, if you want to update your database and scale up its replica then you have to create two separate `PgBouncerOpsRequest`. At first, you have to create a `PgBouncerOpsRequest` for updating. Once it is completed, then you can create another `PgBouncerOpsRequest` for scaling. @@ -260,17 +204,6 @@ If you want to reconfigure your Running PgBouncer cluster or different component - `removeCustomConfig` is a boolean field. Specify this field to true if you want to remove all the custom configuration from the deployed pgbouncer server. -### spec.tls - -If you want to reconfigure the TLS configuration of your pgbouncer cluster i.e. add TLS, remove TLS, update issuer/cluster issuer or Certificates and rotate the certificates, you have to specify `spec.tls` section. This field consists of the following sub-field: - -- `spec.tls.issuerRef` specifies the issuer name, kind and api group. -- `spec.tls.certificates` specifies the certificates. You can learn more about this field from [here](/docs/guides/pgbouncer/concepts/pgbouncer.md#spectls). -- `spec.tls.rotateCertificates` specifies that we want to rotate the certificate of this database. -- `spec.tls.remove` specifies that we want to remove tls from this database. -- `spec.tls.sslMode` specifies what will be the ssl mode of the cluster allowed values are: disable,allow,prefer,require,verify-ca,verify-full -- `spec.tls.clientAuthMode` specifies what will be the client authentication mode of the cluster allowed values are: md5,scram,cert - ### spec.timeout As we internally retry the ops request steps multiple times, This `timeout` field helps the users to specify the timeout for those steps of the ops request (in second). If a step doesn't finish within the specified timeout, the ops request will result in failure. From eb0ceec1782f3cd040c09cbe3072ccd2f0742272 Mon Sep 17 00:00:00 2001 From: Hiranmoy Das Chowdhury Date: Wed, 11 Dec 2024 18:41:00 +0600 Subject: [PATCH 20/29] image added for monitoring Signed-off-by: Hiranmoy Das Chowdhury --- .../pgbouncer/prometheus-operator.png | Bin 0 -> 72921 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/images/day-2-operation/pgbouncer/prometheus-operator.png diff --git a/docs/images/day-2-operation/pgbouncer/prometheus-operator.png b/docs/images/day-2-operation/pgbouncer/prometheus-operator.png new file mode 100644 index 0000000000000000000000000000000000000000..abcb7e8fb6fe1edf88851d6bf2e3af73c025de33 GIT binary patch literal 72921 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

+^c-Ql6c}wSX4VjOwYX_sE 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 literal 0 HcmV?d00001 From 2753d254dd55fd7640a84519414afafdf5d274c7 Mon Sep 17 00:00:00 2001 From: Hiranmoy Das Chowdhury Date: Thu, 12 Dec 2024 11:42:27 +0600 Subject: [PATCH 21/29] pgbouncer wiki change Signed-off-by: Hiranmoy Das Chowdhury --- docs/guides/pgbouncer/concepts/autoscaler.md | 2 +- docs/guides/pgbouncer/concepts/opsrequest.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/guides/pgbouncer/concepts/autoscaler.md b/docs/guides/pgbouncer/concepts/autoscaler.md index 103d91eb5..6a4f144e3 100644 --- a/docs/guides/pgbouncer/concepts/autoscaler.md +++ b/docs/guides/pgbouncer/concepts/autoscaler.md @@ -16,7 +16,7 @@ section_menu_id: guides ## What is PgBouncerAutoscaler -`PgBouncerAutoscaler` is a Kubernetes `Custom Resource Definitions` (CRD). It provides a declarative configuration for autoscaling [PgBouncer](https://pgbouncer.net/mediawiki/index.php/Main_Page) compute resources of PgBouncer components in a Kubernetes native way. +`PgBouncerAutoscaler` is a Kubernetes `Custom Resource Definitions` (CRD). It provides a declarative configuration for autoscaling [PgBouncer](https://www.pgbouncer.org/usage.html) compute resources of PgBouncer components in a Kubernetes native way. ## PgBouncerAutoscaler CRD Specifications diff --git a/docs/guides/pgbouncer/concepts/opsrequest.md b/docs/guides/pgbouncer/concepts/opsrequest.md index 3a209c66d..50430e0b3 100644 --- a/docs/guides/pgbouncer/concepts/opsrequest.md +++ b/docs/guides/pgbouncer/concepts/opsrequest.md @@ -16,7 +16,7 @@ section_menu_id: guides ## What is PgBouncerOpsRequest -`PgBouncerOpsRequest` is a Kubernetes `Custom Resource Definitions` (CRD). It provides a declarative configuration for [PgBouncer](https://pgbouncer.net/mediawiki/index.php/Main_Page) administrative operations like version updating, horizontal scaling, vertical scaling etc. in a Kubernetes native way. +`PgBouncerOpsRequest` is a Kubernetes `Custom Resource Definitions` (CRD). It provides a declarative configuration for [PgBouncer](https://www.pgbouncer.org/usage.html) administrative operations like version updating, horizontal scaling, vertical scaling etc. in a Kubernetes native way. ## PgBouncerOpsRequest CRD Specifications From 652a868c635ebc3b4590c5dd2a508d12005ff161 Mon Sep 17 00:00:00 2001 From: Hiranmoy Das Chowdhury Date: Thu, 12 Dec 2024 12:08:58 +0600 Subject: [PATCH 22/29] autoscaling issue fixed Signed-off-by: Hiranmoy Das Chowdhury --- docs/guides/pgbouncer/autoscaler/compute/_index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guides/pgbouncer/autoscaler/compute/_index.md b/docs/guides/pgbouncer/autoscaler/compute/_index.md index f7a179cf8..9d070e638 100644 --- a/docs/guides/pgbouncer/autoscaler/compute/_index.md +++ b/docs/guides/pgbouncer/autoscaler/compute/_index.md @@ -4,7 +4,7 @@ menu: docs_{{ .version }}: identifier: pb-compute-auto-scaling name: Compute Autoscaling - parent: pp-auto-scaling + parent: pb-auto-scaling weight: 46 menu_name: docs_{{ .version }} --- From de88b4f3ef6c1ec4f2a12c1a02617bdb27a91884 Mon Sep 17 00:00:00 2001 From: Hiranmoy Das Chowdhury Date: Thu, 12 Dec 2024 12:25:07 +0600 Subject: [PATCH 23/29] test Signed-off-by: Hiranmoy Das Chowdhury --- docs/guides/pgbouncer/autoscaler/_index.md | 2 +- docs/guides/pgbouncer/autoscaler/compute/_index.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/guides/pgbouncer/autoscaler/_index.md b/docs/guides/pgbouncer/autoscaler/_index.md index 089e262d6..9cb790c71 100644 --- a/docs/guides/pgbouncer/autoscaler/_index.md +++ b/docs/guides/pgbouncer/autoscaler/_index.md @@ -1,5 +1,5 @@ --- -title: Autoscaling +title: Autoscaling auto menu: docs_{{ .version }}: identifier: pb-auto-scaling diff --git a/docs/guides/pgbouncer/autoscaler/compute/_index.md b/docs/guides/pgbouncer/autoscaler/compute/_index.md index 9d070e638..67db417dd 100644 --- a/docs/guides/pgbouncer/autoscaler/compute/_index.md +++ b/docs/guides/pgbouncer/autoscaler/compute/_index.md @@ -5,6 +5,6 @@ menu: identifier: pb-compute-auto-scaling name: Compute Autoscaling parent: pb-auto-scaling - weight: 46 + weight: 10 menu_name: docs_{{ .version }} --- From 14ac3cada791f4ade890506193de62d2013f585a Mon Sep 17 00:00:00 2001 From: Hiranmoy Das Chowdhury Date: Thu, 12 Dec 2024 12:26:08 +0600 Subject: [PATCH 24/29] test 2 Signed-off-by: Hiranmoy Das Chowdhury --- docs/guides/pgbouncer/autoscaler/_index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/guides/pgbouncer/autoscaler/_index.md b/docs/guides/pgbouncer/autoscaler/_index.md index 9cb790c71..1326161c2 100644 --- a/docs/guides/pgbouncer/autoscaler/_index.md +++ b/docs/guides/pgbouncer/autoscaler/_index.md @@ -1,9 +1,9 @@ --- -title: Autoscaling auto +title: Autoscaling menu: docs_{{ .version }}: identifier: pb-auto-scaling - name: Autoscaling + name: Autoscaling Auto parent: pb-pgbouncer-guides weight: 46 menu_name: docs_{{ .version }} From 0f865699708f3d95c4d7e0e7202d29e3a5f62a34 Mon Sep 17 00:00:00 2001 From: Hiranmoy Das Chowdhury Date: Thu, 12 Dec 2024 12:27:06 +0600 Subject: [PATCH 25/29] test 3 Signed-off-by: Hiranmoy Das Chowdhury --- docs/guides/pgbouncer/autoscaler/_index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guides/pgbouncer/autoscaler/_index.md b/docs/guides/pgbouncer/autoscaler/_index.md index 1326161c2..86f62ac49 100644 --- a/docs/guides/pgbouncer/autoscaler/_index.md +++ b/docs/guides/pgbouncer/autoscaler/_index.md @@ -1,5 +1,5 @@ --- -title: Autoscaling +title: Autoscaling Auto menu: docs_{{ .version }}: identifier: pb-auto-scaling From f40ac29a521decfdcbab5cece9b61efaa5f6aaf7 Mon Sep 17 00:00:00 2001 From: Hiranmoy Das Chowdhury Date: Thu, 12 Dec 2024 14:22:27 +0600 Subject: [PATCH 26/29] update version camel case removed Signed-off-by: Hiranmoy Das Chowdhury --- docs/guides/pgbouncer/autoscaler/_index.md | 4 ++-- docs/guides/pgbouncer/update-version/_index.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/guides/pgbouncer/autoscaler/_index.md b/docs/guides/pgbouncer/autoscaler/_index.md index 86f62ac49..089e262d6 100644 --- a/docs/guides/pgbouncer/autoscaler/_index.md +++ b/docs/guides/pgbouncer/autoscaler/_index.md @@ -1,9 +1,9 @@ --- -title: Autoscaling Auto +title: Autoscaling menu: docs_{{ .version }}: identifier: pb-auto-scaling - name: Autoscaling Auto + name: Autoscaling parent: pb-pgbouncer-guides weight: 46 menu_name: docs_{{ .version }} diff --git a/docs/guides/pgbouncer/update-version/_index.md b/docs/guides/pgbouncer/update-version/_index.md index c8f0ef18d..98ba26503 100644 --- a/docs/guides/pgbouncer/update-version/_index.md +++ b/docs/guides/pgbouncer/update-version/_index.md @@ -3,7 +3,7 @@ title: Updating PgBouncer menu: docs_{{ .version }}: identifier: pb-updating - name: UpdateVersion + name: Update Version parent: pb-pgbouncer-guides weight: 40 menu_name: docs_{{ .version }} From d03be11965d423d1a4d68474c126965619424ed1 Mon Sep 17 00:00:00 2001 From: Hiranmoy Das Chowdhury Date: Thu, 12 Dec 2024 17:32:06 +0600 Subject: [PATCH 27/29] monitoring part is done Signed-off-by: Hiranmoy Das Chowdhury --- .../monitoring/using-builtin-prometheus.md | 26 ++++++++++-------- .../monitoring/using-prometheus-operator.md | 2 +- .../monitoring/pb-builtin-prom-target.png | Bin 52431 -> 0 bytes 3 files changed, 15 insertions(+), 13 deletions(-) delete mode 100644 docs/images/pgbouncer/monitoring/pb-builtin-prom-target.png diff --git a/docs/guides/pgbouncer/monitoring/using-builtin-prometheus.md b/docs/guides/pgbouncer/monitoring/using-builtin-prometheus.md index 606e782eb..88ac9706d 100644 --- a/docs/guides/pgbouncer/monitoring/using-builtin-prometheus.md +++ b/docs/guides/pgbouncer/monitoring/using-builtin-prometheus.md @@ -90,10 +90,10 @@ KubeDB will create a separate stats service with name `{PgBouncer cr name}-stats ```bash $ kubectl get svc -n demo --selector="app.kubernetes.io/instance=builtin-prom-pb" -NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE -builtin-prom-pb ClusterIP 10.96.124.220 9999/TCP,9595/TCP 2m20s -builtin-prom-pb-pods ClusterIP None 9999/TCP 2m20s -builtin-prom-pb-stats ClusterIP 10.96.132.175 9719/TCP 2m20s +NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE +builtin-prom-pb ClusterIP 10.96.210.2 5432/TCP 86s +builtin-prom-pb-pods ClusterIP None 5432/TCP 86s +builtin-prom-pb-stats ClusterIP 10.96.215.193 56790/TCP 74s ``` Here, `builtin-prom-pb-stats` service has been created for monitoring purpose. Let's describe the service. @@ -106,19 +106,20 @@ Labels: app.kubernetes.io/component=connection-pooler app.kubernetes.io/instance=builtin-prom-pb app.kubernetes.io/managed-by=kubedb.com app.kubernetes.io/name=pgbouncers.kubedb.com + kubedb.com/role=stats Annotations: monitoring.appscode.com/agent: prometheus.io/builtin prometheus.io/path: /metrics - prometheus.io/port: 9719 + prometheus.io/port: 56790 prometheus.io/scrape: true -Selector: apb.kubernetes.io/instance=builtin-prom-pb,app.kubernetes.io/managed-by=kubedb.com,app.kubernetes.io/name=pgbouncers.kubedb.com +Selector: app.kubernetes.io/instance=builtin-prom-pb,app.kubernetes.io/managed-by=kubedb.com,app.kubernetes.io/name=pgbouncers.kubedb.com Type: ClusterIP IP Family Policy: SingleStack IP Families: IPv4 -IP: 10.96.132.175 -IPs: 10.96.132.175 -Port: metrics 9719/TCP +IP: 10.96.215.193 +IPs: 10.96.215.193 +Port: metrics 56790/TCP TargetPort: metrics/TCP -Endpoints: 10.244.0.27:9719 +Endpoints: 10.244.0.28:56790 Session Affinity: None Events: ``` @@ -127,7 +128,7 @@ You can see that the service contains following annotations. ```bash prometheus.io/path: /metrics -prometheus.io/port: 9719 +prometheus.io/port: 56790 prometheus.io/scrape: true ``` @@ -336,9 +337,10 @@ Forwarding from [::1]:9090 -> 9090 Now, we can access the dashboard at `localhost:9090`. Open [http://localhost:9090](http://localhost:9090) in your browser. You should see the endpoint of `builtin-prom-pb-stats` service as one of the targets.

-   +  Prometheus Target

+ Check the labels marked with red rectangle. These labels confirm that the metrics are coming from `PgBouncer` database `builtin-prom-pb` through stats service `builtin-prom-pb-stats`. Now, you can view the collected metrics and create a graph from homepage of this Prometheus dashboard. You can also use this Prometheus server as data source for [Grafana](https://grafana.com/) and create beautiful dashboard with collected metrics. diff --git a/docs/guides/pgbouncer/monitoring/using-prometheus-operator.md b/docs/guides/pgbouncer/monitoring/using-prometheus-operator.md index ba97c0548..a12813466 100644 --- a/docs/guides/pgbouncer/monitoring/using-prometheus-operator.md +++ b/docs/guides/pgbouncer/monitoring/using-prometheus-operator.md @@ -231,7 +231,7 @@ coreos-prom-pb kubedb.com/v1 1.18.0 Ready 65s KubeDB will create a separate stats service with name `{PgBouncer crd name}-stats` for monitoring purpose. ```bash -$ kubectl get svc -n demo --selector="app.kubernetes.io/instance=coreos-prom-pp" +$ kubectl get svc -n demo --selector="app.kubernetes.io/instance=coreos-prom-pb" NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE coreos-prom-pb ClusterIP 10.96.201.180 9999/TCP,9595/TCP 4m3s coreos-prom-pb-pods ClusterIP None 9999/TCP 4m3s diff --git a/docs/images/pgbouncer/monitoring/pb-builtin-prom-target.png b/docs/images/pgbouncer/monitoring/pb-builtin-prom-target.png deleted file mode 100644 index c7f19ea488629a871ecbda0738ae1504b73c1fe8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 52431 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 cwD|!s`w-4#$(KzA26{<|$bPT Date: Thu, 12 Dec 2024 17:43:04 +0600 Subject: [PATCH 28/29] image changes for monitoring Signed-off-by: Hiranmoy Das Chowdhury --- .../monitoring/using-builtin-prometheus.md | 3 +-- .../monitoring/pb-builtin-prom-target.png | Bin 0 -> 56304 bytes 2 files changed, 1 insertion(+), 2 deletions(-) create mode 100644 docs/images/pgbouncer/monitoring/pb-builtin-prom-target.png diff --git a/docs/guides/pgbouncer/monitoring/using-builtin-prometheus.md b/docs/guides/pgbouncer/monitoring/using-builtin-prometheus.md index 88ac9706d..ef1e0bb17 100644 --- a/docs/guides/pgbouncer/monitoring/using-builtin-prometheus.md +++ b/docs/guides/pgbouncer/monitoring/using-builtin-prometheus.md @@ -337,10 +337,9 @@ Forwarding from [::1]:9090 -> 9090 Now, we can access the dashboard at `localhost:9090`. Open [http://localhost:9090](http://localhost:9090) in your browser. You should see the endpoint of `builtin-prom-pb-stats` service as one of the targets.

-  Prometheus Target +  Prometheus Target

- Check the labels marked with red rectangle. These labels confirm that the metrics are coming from `PgBouncer` database `builtin-prom-pb` through stats service `builtin-prom-pb-stats`. Now, you can view the collected metrics and create a graph from homepage of this Prometheus dashboard. You can also use this Prometheus server as data source for [Grafana](https://grafana.com/) and create beautiful dashboard with collected metrics. diff --git a/docs/images/pgbouncer/monitoring/pb-builtin-prom-target.png b/docs/images/pgbouncer/monitoring/pb-builtin-prom-target.png new file mode 100644 index 0000000000000000000000000000000000000000..882e0b29ceeccc311b06709e8504b51c01ad6808 GIT binary patch literal 56304 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 Date: Thu, 12 Dec 2024 18:45:12 +0600 Subject: [PATCH 29/29] camecase solved Signed-off-by: Hiranmoy Das Chowdhury --- docs/guides/pgbouncer/update-version/update_version.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guides/pgbouncer/update-version/update_version.md b/docs/guides/pgbouncer/update-version/update_version.md index 0a9110d6e..a408d0969 100644 --- a/docs/guides/pgbouncer/update-version/update_version.md +++ b/docs/guides/pgbouncer/update-version/update_version.md @@ -3,7 +3,7 @@ title: Updating PgBouncer menu: docs_{{ .version }}: identifier: pb-updating-pgbouncer - name: updatingPgBouncer + name: Updating PgBouncer parent: pb-updating weight: 20 menu_name: docs_{{ .version }}