From 42e878781e9470b2be048aec4f3bb8809a33a085 Mon Sep 17 00:00:00 2001 From: Abu Sayed <82162518+sayedppqq@users.noreply.github.com> Date: Wed, 18 Dec 2024 00:36:45 +0600 Subject: [PATCH] FerretDB Complete Doc (#696) * wip Signed-off-by: sayedppqq * add some pages and wip Signed-off-by: sayedppqq * wip * wip Signed-off-by: sayedppqq * fix link Signed-off-by: sayedppqq * fix concepts Signed-off-by: sayedppqq --------- Signed-off-by: sayedppqq Co-authored-by: Md. Anisur Rahman <54911684+anisurrahman75@users.noreply.github.com> --- .../autoscaling/compute/autoscaler.yaml | 21 + .../compute/ferretdb-autoscale.yaml | 28 + .../ferretdb/monitoring/builtin-prom-fr.yaml | 19 + .../ferretdb/monitoring/coreos-prom-fr.yaml | 24 + .../ferretdb/reconfigure-tls/ferretdb.yaml | 16 + .../reconfigure-tls/frops-add-tls.yaml | 16 + .../reconfigure-tls/frops-change-issuer.yaml | 14 + .../reconfigure-tls/frops-remove.yaml | 11 + .../reconfigure-tls/frops-rotate.yaml | 11 + .../ferretdb/reconfigure-tls/issuer.yaml | 8 + .../ferretdb/reconfigure-tls/new-issuer.yaml | 8 + docs/examples/ferretdb/restart/ferretdb.yaml | 17 + docs/examples/ferretdb/restart/ops.yaml | 11 + .../ferretdb/scaling/fr-horizontal.yaml | 17 + .../ferretdb/scaling/fr-vertical-ops.yaml | 20 + .../ferretdb/scaling/fr-vertical.yaml | 17 + .../scaling/frops-hscale-down-ops.yaml | 11 + .../ferretdb/scaling/frops-hscale-up-ops.yaml | 11 + docs/examples/ferretdb/tls/ferretdb-tls.yaml | 25 + docs/examples/ferretdb/tls/issuer.yaml | 8 + .../ferretdb/update-version/fr-update.yaml | 17 + .../ferretdb/update-version/frops-update.yaml | 11 + docs/guides/ferretdb/autoscaler/_index.md | 10 + .../ferretdb/autoscaler/compute/_index.md | 10 + .../autoscaler/compute/compute-autoscale.md | 406 +++++++ .../ferretdb/autoscaler/compute/overview.md | 55 + docs/guides/ferretdb/concepts/_index.md | 10 + docs/guides/ferretdb/concepts/appbinding.md | 147 +++ docs/guides/ferretdb/concepts/autoscaler.md | 73 ++ docs/guides/ferretdb/concepts/catalog.md | 74 ++ docs/guides/ferretdb/concepts/ferretdb.md | 298 +++++ docs/guides/ferretdb/concepts/opsrequest.md | 258 ++++ docs/guides/ferretdb/monitoring/_index.md | 10 + docs/guides/ferretdb/monitoring/overview.md | 91 ++ .../monitoring/using-builtin-prometheus.md | 366 ++++++ .../monitoring/using-prometheus-operator.md | 362 ++++++ docs/guides/ferretdb/quickstart/quickstart.md | 13 +- .../guides/ferretdb/reconfigure-tls/_index.md | 10 + .../ferretdb/reconfigure-tls/overview.md | 54 + .../reconfigure-tls/reconfigure-tls.md | 1072 +++++++++++++++++ docs/guides/ferretdb/restart/_index.md | 10 + docs/guides/ferretdb/restart/restart.md | 174 +++ docs/guides/ferretdb/scaling/_index.md | 10 + .../scaling/horizontal-scaling/_index.md | 10 + .../horizontal-scaling/horizontal-ops.md | 432 +++++++ .../scaling/horizontal-scaling/overview.md | 54 + .../scaling/vertical-scaling/_index.md | 10 + .../scaling/vertical-scaling/overview.md | 54 + .../scaling/vertical-scaling/vertical-ops.md | 282 +++++ docs/guides/ferretdb/tls/_index.md | 10 + docs/guides/ferretdb/tls/configure_tls.md | 244 ++++ docs/guides/ferretdb/tls/overview.md | 70 ++ docs/guides/ferretdb/update-version/_index.md | 10 + .../ferretdb/update-version/overview.md | 54 + .../ferretdb/update-version/update-version.md | 241 ++++ docs/guides/mongodb/concepts/mongodb.md | 2 +- docs/guides/pgpool/concepts/pgpool.md | 2 +- .../pgpool/update-version/update_version.md | 4 +- .../ferretdb/fr-builtin-prom-target.png | Bin 0 -> 66375 bytes .../ferretdb/fr-compute-autoscaling.svg | 4 + .../images/ferretdb/fr-coreos-prom-target.png | Bin 0 -> 137484 bytes .../images/ferretdb/fr-horizontal-scaling.svg | 4 + docs/images/ferretdb/fr-reconfigure-tls.svg | 4 + docs/images/ferretdb/fr-tls.svg | 4 + docs/images/ferretdb/fr-update.svg | 4 + docs/images/ferretdb/fr-vertical-scaling.svg | 4 + 66 files changed, 5345 insertions(+), 12 deletions(-) create mode 100644 docs/examples/ferretdb/autoscaling/compute/autoscaler.yaml create mode 100644 docs/examples/ferretdb/autoscaling/compute/ferretdb-autoscale.yaml create mode 100644 docs/examples/ferretdb/monitoring/builtin-prom-fr.yaml create mode 100644 docs/examples/ferretdb/monitoring/coreos-prom-fr.yaml create mode 100644 docs/examples/ferretdb/reconfigure-tls/ferretdb.yaml create mode 100644 docs/examples/ferretdb/reconfigure-tls/frops-add-tls.yaml create mode 100644 docs/examples/ferretdb/reconfigure-tls/frops-change-issuer.yaml create mode 100644 docs/examples/ferretdb/reconfigure-tls/frops-remove.yaml create mode 100644 docs/examples/ferretdb/reconfigure-tls/frops-rotate.yaml create mode 100644 docs/examples/ferretdb/reconfigure-tls/issuer.yaml create mode 100644 docs/examples/ferretdb/reconfigure-tls/new-issuer.yaml create mode 100644 docs/examples/ferretdb/restart/ferretdb.yaml create mode 100644 docs/examples/ferretdb/restart/ops.yaml create mode 100644 docs/examples/ferretdb/scaling/fr-horizontal.yaml create mode 100644 docs/examples/ferretdb/scaling/fr-vertical-ops.yaml create mode 100644 docs/examples/ferretdb/scaling/fr-vertical.yaml create mode 100644 docs/examples/ferretdb/scaling/frops-hscale-down-ops.yaml create mode 100644 docs/examples/ferretdb/scaling/frops-hscale-up-ops.yaml create mode 100644 docs/examples/ferretdb/tls/ferretdb-tls.yaml create mode 100644 docs/examples/ferretdb/tls/issuer.yaml create mode 100644 docs/examples/ferretdb/update-version/fr-update.yaml create mode 100644 docs/examples/ferretdb/update-version/frops-update.yaml create mode 100644 docs/guides/ferretdb/autoscaler/_index.md create mode 100644 docs/guides/ferretdb/autoscaler/compute/_index.md create mode 100644 docs/guides/ferretdb/autoscaler/compute/compute-autoscale.md create mode 100644 docs/guides/ferretdb/autoscaler/compute/overview.md create mode 100644 docs/guides/ferretdb/concepts/_index.md create mode 100644 docs/guides/ferretdb/concepts/appbinding.md create mode 100644 docs/guides/ferretdb/concepts/autoscaler.md create mode 100644 docs/guides/ferretdb/concepts/catalog.md create mode 100644 docs/guides/ferretdb/concepts/ferretdb.md create mode 100644 docs/guides/ferretdb/concepts/opsrequest.md create mode 100644 docs/guides/ferretdb/monitoring/_index.md create mode 100644 docs/guides/ferretdb/monitoring/overview.md create mode 100644 docs/guides/ferretdb/monitoring/using-builtin-prometheus.md create mode 100644 docs/guides/ferretdb/monitoring/using-prometheus-operator.md create mode 100644 docs/guides/ferretdb/reconfigure-tls/_index.md create mode 100644 docs/guides/ferretdb/reconfigure-tls/overview.md create mode 100644 docs/guides/ferretdb/reconfigure-tls/reconfigure-tls.md create mode 100644 docs/guides/ferretdb/restart/_index.md create mode 100644 docs/guides/ferretdb/restart/restart.md create mode 100644 docs/guides/ferretdb/scaling/_index.md create mode 100644 docs/guides/ferretdb/scaling/horizontal-scaling/_index.md create mode 100644 docs/guides/ferretdb/scaling/horizontal-scaling/horizontal-ops.md create mode 100644 docs/guides/ferretdb/scaling/horizontal-scaling/overview.md create mode 100644 docs/guides/ferretdb/scaling/vertical-scaling/_index.md create mode 100644 docs/guides/ferretdb/scaling/vertical-scaling/overview.md create mode 100644 docs/guides/ferretdb/scaling/vertical-scaling/vertical-ops.md create mode 100644 docs/guides/ferretdb/tls/_index.md create mode 100644 docs/guides/ferretdb/tls/configure_tls.md create mode 100644 docs/guides/ferretdb/tls/overview.md create mode 100644 docs/guides/ferretdb/update-version/_index.md create mode 100644 docs/guides/ferretdb/update-version/overview.md create mode 100644 docs/guides/ferretdb/update-version/update-version.md create mode 100644 docs/images/ferretdb/fr-builtin-prom-target.png create mode 100644 docs/images/ferretdb/fr-compute-autoscaling.svg create mode 100644 docs/images/ferretdb/fr-coreos-prom-target.png create mode 100644 docs/images/ferretdb/fr-horizontal-scaling.svg create mode 100644 docs/images/ferretdb/fr-reconfigure-tls.svg create mode 100644 docs/images/ferretdb/fr-tls.svg create mode 100644 docs/images/ferretdb/fr-update.svg create mode 100644 docs/images/ferretdb/fr-vertical-scaling.svg diff --git a/docs/examples/ferretdb/autoscaling/compute/autoscaler.yaml b/docs/examples/ferretdb/autoscaling/compute/autoscaler.yaml new file mode 100644 index 0000000000..84149d5468 --- /dev/null +++ b/docs/examples/ferretdb/autoscaling/compute/autoscaler.yaml @@ -0,0 +1,21 @@ +apiVersion: autoscaling.kubedb.com/v1alpha1 +kind: FerretDBAutoscaler +metadata: + name: ferretdb-autoscale-ops + namespace: demo +spec: + databaseRef: + name: ferretdb-autoscale + compute: + ferretdb: + 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/examples/ferretdb/autoscaling/compute/ferretdb-autoscale.yaml b/docs/examples/ferretdb/autoscaling/compute/ferretdb-autoscale.yaml new file mode 100644 index 0000000000..c33bfa99d7 --- /dev/null +++ b/docs/examples/ferretdb/autoscaling/compute/ferretdb-autoscale.yaml @@ -0,0 +1,28 @@ +apiVersion: kubedb.com/v1alpha2 +kind: FerretDB +metadata: + name: ferretdb-autoscale + namespace: demo +spec: + version: "1.23.0" + replicas: 1 + backend: + externallyManaged: false + podTemplate: + spec: + containers: + - name: ferretdb + resources: + requests: + cpu: "200m" + memory: "300Mi" + limits: + cpu: "200m" + memory: "300Mi" + storage: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 500Mi + deletionPolicy: WipeOut \ No newline at end of file diff --git a/docs/examples/ferretdb/monitoring/builtin-prom-fr.yaml b/docs/examples/ferretdb/monitoring/builtin-prom-fr.yaml new file mode 100644 index 0000000000..df3b0d3f31 --- /dev/null +++ b/docs/examples/ferretdb/monitoring/builtin-prom-fr.yaml @@ -0,0 +1,19 @@ +apiVersion: kubedb.com/v1alpha2 +kind: FerretDB +metadata: + name: builtin-prom-fr + namespace: demo +spec: + version: "1.23.0" + storage: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 500Mi + backend: + externallyManaged: false + deletionPolicy: WipeOut + replicas: 2 + monitor: + agent: prometheus.io/builtin \ No newline at end of file diff --git a/docs/examples/ferretdb/monitoring/coreos-prom-fr.yaml b/docs/examples/ferretdb/monitoring/coreos-prom-fr.yaml new file mode 100644 index 0000000000..f122b73343 --- /dev/null +++ b/docs/examples/ferretdb/monitoring/coreos-prom-fr.yaml @@ -0,0 +1,24 @@ +apiVersion: kubedb.com/v1alpha2 +kind: FerretDB +metadata: + name: coreos-prom-fr + namespace: demo +spec: + version: "1.18.0" + storage: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 500Mi + backend: + externallyManaged: false + deletionPolicy: WipeOut + replicas: 2 + monitor: + agent: prometheus.io/operator + prometheus: + serviceMonitor: + labels: + release: prometheus + interval: 10s \ No newline at end of file diff --git a/docs/examples/ferretdb/reconfigure-tls/ferretdb.yaml b/docs/examples/ferretdb/reconfigure-tls/ferretdb.yaml new file mode 100644 index 0000000000..09b7b0f9ef --- /dev/null +++ b/docs/examples/ferretdb/reconfigure-tls/ferretdb.yaml @@ -0,0 +1,16 @@ +apiVersion: kubedb.com/v1alpha2 +kind: FerretDB +metadata: + name: ferretdb + namespace: demo +spec: + version: "1.18.0" + storage: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 500Mi + backend: + externallyManaged: false + replicas: 2 \ No newline at end of file diff --git a/docs/examples/ferretdb/reconfigure-tls/frops-add-tls.yaml b/docs/examples/ferretdb/reconfigure-tls/frops-add-tls.yaml new file mode 100644 index 0000000000..7af90dc77c --- /dev/null +++ b/docs/examples/ferretdb/reconfigure-tls/frops-add-tls.yaml @@ -0,0 +1,16 @@ +apiVersion: ops.kubedb.com/v1alpha1 +kind: FerretDBOpsRequest +metadata: + name: frops-add-tls + namespace: demo +spec: + type: ReconfigureTLS + databaseRef: + name: ferretdb + tls: + issuerRef: + name: ferretdb-ca-issuer + kind: Issuer + apiGroup: "cert-manager.io" + timeout: 5m + apply: IfReady \ No newline at end of file diff --git a/docs/examples/ferretdb/reconfigure-tls/frops-change-issuer.yaml b/docs/examples/ferretdb/reconfigure-tls/frops-change-issuer.yaml new file mode 100644 index 0000000000..38df852bbb --- /dev/null +++ b/docs/examples/ferretdb/reconfigure-tls/frops-change-issuer.yaml @@ -0,0 +1,14 @@ +apiVersion: ops.kubedb.com/v1alpha1 +kind: FerretDBOpsRequest +metadata: + name: frops-change-issuer + namespace: demo +spec: + type: ReconfigureTLS + databaseRef: + name: ferretdb + tls: + issuerRef: + name: fr-new-issuer + kind: Issuer + apiGroup: "cert-manager.io" \ No newline at end of file diff --git a/docs/examples/ferretdb/reconfigure-tls/frops-remove.yaml b/docs/examples/ferretdb/reconfigure-tls/frops-remove.yaml new file mode 100644 index 0000000000..b2708bac37 --- /dev/null +++ b/docs/examples/ferretdb/reconfigure-tls/frops-remove.yaml @@ -0,0 +1,11 @@ +apiVersion: ops.kubedb.com/v1alpha1 +kind: FerretDBOpsRequest +metadata: + name: frops-remove + namespace: demo +spec: + type: ReconfigureTLS + databaseRef: + name: ferretdb + tls: + remove: true \ No newline at end of file diff --git a/docs/examples/ferretdb/reconfigure-tls/frops-rotate.yaml b/docs/examples/ferretdb/reconfigure-tls/frops-rotate.yaml new file mode 100644 index 0000000000..dd0153c5a2 --- /dev/null +++ b/docs/examples/ferretdb/reconfigure-tls/frops-rotate.yaml @@ -0,0 +1,11 @@ +apiVersion: ops.kubedb.com/v1alpha1 +kind: FerretDBOpsRequest +metadata: + name: frops-rotate + namespace: demo +spec: + type: ReconfigureTLS + databaseRef: + name: ferretdb + tls: + rotateCertificates: true \ No newline at end of file diff --git a/docs/examples/ferretdb/reconfigure-tls/issuer.yaml b/docs/examples/ferretdb/reconfigure-tls/issuer.yaml new file mode 100644 index 0000000000..21558c9037 --- /dev/null +++ b/docs/examples/ferretdb/reconfigure-tls/issuer.yaml @@ -0,0 +1,8 @@ +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: ferretdb-ca-issuer + namespace: demo +spec: + ca: + secretName: ferretdb-ca \ No newline at end of file diff --git a/docs/examples/ferretdb/reconfigure-tls/new-issuer.yaml b/docs/examples/ferretdb/reconfigure-tls/new-issuer.yaml new file mode 100644 index 0000000000..71d30275ee --- /dev/null +++ b/docs/examples/ferretdb/reconfigure-tls/new-issuer.yaml @@ -0,0 +1,8 @@ +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: fr-new-issuer + namespace: demo +spec: + ca: + secretName: ferretdb-new-ca \ No newline at end of file diff --git a/docs/examples/ferretdb/restart/ferretdb.yaml b/docs/examples/ferretdb/restart/ferretdb.yaml new file mode 100644 index 0000000000..b86eab911a --- /dev/null +++ b/docs/examples/ferretdb/restart/ferretdb.yaml @@ -0,0 +1,17 @@ +apiVersion: kubedb.com/v1alpha2 +kind: FerretDB +metadata: + name: ferretdb + namespace: demo +spec: + version: "1.23.0" + replicas: 1 + backend: + externallyManaged: false + storage: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 500Mi + deletionPolicy: WipeOut \ No newline at end of file diff --git a/docs/examples/ferretdb/restart/ops.yaml b/docs/examples/ferretdb/restart/ops.yaml new file mode 100644 index 0000000000..a8f7e3a8c4 --- /dev/null +++ b/docs/examples/ferretdb/restart/ops.yaml @@ -0,0 +1,11 @@ +apiVersion: ops.kubedb.com/v1alpha1 +kind: FerretDBOpsRequest +metadata: + name: restart-ferretdb + namespace: demo +spec: + type: Restart + databaseRef: + name: ferretdb + timeout: 3m + apply: Always \ No newline at end of file diff --git a/docs/examples/ferretdb/scaling/fr-horizontal.yaml b/docs/examples/ferretdb/scaling/fr-horizontal.yaml new file mode 100644 index 0000000000..bc542d43b9 --- /dev/null +++ b/docs/examples/ferretdb/scaling/fr-horizontal.yaml @@ -0,0 +1,17 @@ +apiVersion: kubedb.com/v1alpha2 +kind: FerretDB +metadata: + name: fr-horizontal + namespace: demo +spec: + version: "1.23.0" + replicas: 1 + backend: + externallyManaged: false + storage: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 500Mi + deletionPolicy: WipeOut \ No newline at end of file diff --git a/docs/examples/ferretdb/scaling/fr-vertical-ops.yaml b/docs/examples/ferretdb/scaling/fr-vertical-ops.yaml new file mode 100644 index 0000000000..e5de4d2b39 --- /dev/null +++ b/docs/examples/ferretdb/scaling/fr-vertical-ops.yaml @@ -0,0 +1,20 @@ +apiVersion: ops.kubedb.com/v1alpha1 +kind: FerretDBOpsRequest +metadata: + name: ferretdb-scale-vertical + namespace: demo +spec: + type: VerticalScaling + databaseRef: + name: fr-vertical + verticalScaling: + node: + 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/ferretdb/scaling/fr-vertical.yaml b/docs/examples/ferretdb/scaling/fr-vertical.yaml new file mode 100644 index 0000000000..6809284bfb --- /dev/null +++ b/docs/examples/ferretdb/scaling/fr-vertical.yaml @@ -0,0 +1,17 @@ +apiVersion: kubedb.com/v1alpha2 +kind: FerretDB +metadata: + name: fr-vertical + namespace: demo +spec: + version: "1.23.0" + replicas: 1 + backend: + externallyManaged: false + storage: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 500Mi + deletionPolicy: WipeOut \ No newline at end of file diff --git a/docs/examples/ferretdb/scaling/frops-hscale-down-ops.yaml b/docs/examples/ferretdb/scaling/frops-hscale-down-ops.yaml new file mode 100644 index 0000000000..c51199639e --- /dev/null +++ b/docs/examples/ferretdb/scaling/frops-hscale-down-ops.yaml @@ -0,0 +1,11 @@ +apiVersion: ops.kubedb.com/v1alpha1 +kind: FerretDBOpsRequest +metadata: + name: ferretdb-horizontal-scale-down + namespace: demo +spec: + type: HorizontalScaling + databaseRef: + name: fr-horizontal + horizontalScaling: + node: 2 \ No newline at end of file diff --git a/docs/examples/ferretdb/scaling/frops-hscale-up-ops.yaml b/docs/examples/ferretdb/scaling/frops-hscale-up-ops.yaml new file mode 100644 index 0000000000..b8c998e354 --- /dev/null +++ b/docs/examples/ferretdb/scaling/frops-hscale-up-ops.yaml @@ -0,0 +1,11 @@ +apiVersion: ops.kubedb.com/v1alpha1 +kind: FerretDBOpsRequest +metadata: + name: ferretdb-horizontal-scale-up + namespace: demo +spec: + type: HorizontalScaling + databaseRef: + name: fr-horizontal + horizontalScaling: + node: 3 \ No newline at end of file diff --git a/docs/examples/ferretdb/tls/ferretdb-tls.yaml b/docs/examples/ferretdb/tls/ferretdb-tls.yaml new file mode 100644 index 0000000000..1f3e2c1d5b --- /dev/null +++ b/docs/examples/ferretdb/tls/ferretdb-tls.yaml @@ -0,0 +1,25 @@ +apiVersion: kubedb.com/v1alpha2 +kind: FerretDB +metadata: + name: fr-tls + namespace: demo +spec: + version: "1.23.0" + authSecret: + externallyManaged: false + storage: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 500Mi + backend: + externallyManaged: false + deletionPolicy: WipeOut + replicas: 1 + sslMode: requireSSL + tls: + issuerRef: + apiGroup: "cert-manager.io" + kind: Issuer + name: ferretdb-ca-issuer \ No newline at end of file diff --git a/docs/examples/ferretdb/tls/issuer.yaml b/docs/examples/ferretdb/tls/issuer.yaml new file mode 100644 index 0000000000..21558c9037 --- /dev/null +++ b/docs/examples/ferretdb/tls/issuer.yaml @@ -0,0 +1,8 @@ +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: ferretdb-ca-issuer + namespace: demo +spec: + ca: + secretName: ferretdb-ca \ No newline at end of file diff --git a/docs/examples/ferretdb/update-version/fr-update.yaml b/docs/examples/ferretdb/update-version/fr-update.yaml new file mode 100644 index 0000000000..5759c89db6 --- /dev/null +++ b/docs/examples/ferretdb/update-version/fr-update.yaml @@ -0,0 +1,17 @@ +apiVersion: kubedb.com/v1alpha2 +kind: FerretDB +metadata: + name: fr-update + namespace: demo +spec: + version: "1.18.0" + replicas: 1 + backend: + externallyManaged: false + storage: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 500Mi + deletionPolicy: WipeOut \ No newline at end of file diff --git a/docs/examples/ferretdb/update-version/frops-update.yaml b/docs/examples/ferretdb/update-version/frops-update.yaml new file mode 100644 index 0000000000..9b66daaf14 --- /dev/null +++ b/docs/examples/ferretdb/update-version/frops-update.yaml @@ -0,0 +1,11 @@ +apiVersion: ops.kubedb.com/v1alpha1 +kind: FerretDBOpsRequest +metadata: + name: ferretdb-version-update + namespace: demo +spec: + type: UpdateVersion + databaseRef: + name: fr-update + updateVersion: + targetVersion: 1.23.0 \ No newline at end of file diff --git a/docs/guides/ferretdb/autoscaler/_index.md b/docs/guides/ferretdb/autoscaler/_index.md new file mode 100644 index 0000000000..9c3849b4cc --- /dev/null +++ b/docs/guides/ferretdb/autoscaler/_index.md @@ -0,0 +1,10 @@ +--- +title: Autoscaling +menu: + docs_{{ .version }}: + identifier: fr-auto-scaling + name: Autoscaling + parent: fr-ferretdb-guides + weight: 46 +menu_name: docs_{{ .version }} +--- \ No newline at end of file diff --git a/docs/guides/ferretdb/autoscaler/compute/_index.md b/docs/guides/ferretdb/autoscaler/compute/_index.md new file mode 100644 index 0000000000..073175de71 --- /dev/null +++ b/docs/guides/ferretdb/autoscaler/compute/_index.md @@ -0,0 +1,10 @@ +--- +title: Compute Autoscaling +menu: + docs_{{ .version }}: + identifier: fr-compute-auto-scaling + name: Compute Autoscaling + parent: fr-auto-scaling + weight: 46 +menu_name: docs_{{ .version }} +--- \ No newline at end of file diff --git a/docs/guides/ferretdb/autoscaler/compute/compute-autoscale.md b/docs/guides/ferretdb/autoscaler/compute/compute-autoscale.md new file mode 100644 index 0000000000..a51212656d --- /dev/null +++ b/docs/guides/ferretdb/autoscaler/compute/compute-autoscale.md @@ -0,0 +1,406 @@ +--- +title: FerretDB Autoscaling +menu: + docs_{{ .version }}: + identifier: fr-auto-scaling-ferretdb + name: Ferretdb Compute Autoscaling + parent: fr-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 FerretDB + +This guide will show you how to use `KubeDB` to autoscale compute resources i.e. cpu and memory of a FerretDB. + +## 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: + - [FerretDB](/docs/guides/ferretdb/concepts/ferretdb.md) + - [FerretDBAutoscaler](/docs/guides/ferretdb/concepts/autoscaler.md) + - [FerretDBOpsRequest](/docs/guides/ferretdb/concepts/opsrequest.md) + - [Compute Resource Autoscaling Overview](/docs/guides/ferretdb/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/ferretdb](/docs/examples/ferretdb) directory of [kubedb/docs](https://github.com/kubedb/docs) repository. + +## Autoscaling of FerretDB + +Here, we are going to deploy a `FerretDB` standalone using a supported version by `KubeDB` operator. Backend postgres of this FerretDB will be internally managed by KubeDB, or you can use any externally managed postgres but in that case you need to create an [appbinding](/docs/guides/ferretdb/concepts/appbinding.md) yourself. +Then we are going to apply `FerretDBAutoscaler` to set up autoscaling. + +#### Deploy FerretDB + +In this section, we are going to deploy a FerretDB with version `1.23.0` Then, in the next section we will set up autoscaling for this ferretdb using `FerretDBAutoscaler` CRD. Below is the YAML of the `FerretDB` CR that we are going to create, + +```yaml +apiVersion: kubedb.com/v1alpha2 +kind: FerretDB +metadata: + name: ferretdb-autoscale + namespace: demo +spec: + version: "1.23.0" + replicas: 1 + backend: + externallyManaged: false + podTemplate: + spec: + containers: + - name: ferretdb + resources: + requests: + cpu: "200m" + memory: "300Mi" + limits: + cpu: "200m" + memory: "300Mi" + storage: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 500Mi + deletionPolicy: WipeOut +``` + +Let's create the `FerretDB` CRO we have shown above, + +```bash +$ kubectl create -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/ferretdb/autoscaling/compute/ferretdb-autoscale.yaml +ferretdb.kubedb.com/ferretdb-autoscale created +``` + +Now, wait until `ferretdb-autoscale` has status `Ready`. i.e, + +```bash +$ kubectl get fr -n demo +NAME NAMESPACE VERSION STATUS AGE +ferretdb-autoscale demo 1.23.0 Ready 6m1s +``` + +Let's check the FerretDB resources, +```bash +$ kubectl get ferretdb -n demo ferretdb-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 ferretdb. + +We are now ready to apply the `FerretDBAutoscaler` 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 FerretDBAutoscaler Object. + +#### Create FerretDBAutoscaler Object + +In order to set up compute resource autoscaling for this ferretdb, we have to create a `FerretDBAutoscaler` CRO with our desired configuration. Below is the YAML of the `FerretDBAutoscaler` object that we are going to create, + +```yaml +apiVersion: autoscaling.kubedb.com/v1alpha1 +kind: FerretDBAutoscaler +metadata: + name: ferretdb-autoscale-ops + namespace: demo +spec: + databaseRef: + name: ferretdb-autoscale + compute: + ferretdb: + 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 `ferretdb-autoscale`. +- `spec.compute.ferretdb.trigger` specifies that compute resource autoscaling is enabled for this ferretdb. +- `spec.compute.ferretdb.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.ferretdb.minAllowed` specifies the minimum allowed resources for this ferretdb. +- `spec.compute.ferretdb.maxAllowed` specifies the maximum allowed resources for this ferretdb. +- `spec.compute.ferretdb.controlledResources` specifies the resources that are controlled by the autoscaler. +- `spec.compute.ferretdb.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/ferretdb/concepts/opsrequest.md#spectimeout), [apply](/docs/guides/ferretdb/concepts/opsrequest.md#specapply). + +Let's create the `FerretDBAutoscaler` CR we have shown above, + +```bash +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/ferretdb/autoscaling/compute/autoscaler.yaml +ferretdbautoscaler.autoscaling.kubedb.com/ferretdb-autoscaler-ops created +``` + +#### Verify Autoscaling is set up successfully + +Let's check that the `ferretdbautoscaler` resource is created successfully, + +```bash +$ kubectl get ferretdbautoscaler -n demo +NAME AGE +ferretdb-autoscale-ops 6m55s + +$ kubectl describe ferretdbautoscaler ferretdb-autoscale-ops -n demo +Name: ferretdb-autoscale-ops +Namespace: demo +Labels: +Annotations: +API Version: autoscaling.kubedb.com/v1alpha1 +Kind: FerretDBAutoscaler +Metadata: + Creation Timestamp: 2024-10-14T08:30:37Z + Generation: 1 + Resource Version: 11066 + UID: 62387d58-1cd2-4cb6-9d97-91515531fcea +Spec: + Compute: + Ferretdb: + Container Controlled Values: RequestsAndLimits + Controlled Resources: + cpu + memory + Max Allowed: + Cpu: 1 + Memory: 1Gi + Min Allowed: + Cpu: 400m + Memory: 400Mi + Pod Life Time Threshold: 5m + Resource Diff Percentage: 20 + Trigger: On + Database Ref: + Name: ferretdb-autoscale +Status: + Checkpoints: + Cpu Histogram: + Bucket Weights: + Index: 0 + Weight: 10000 + Reference Timestamp: 2024-10-14T08:30:00Z + Total Weight: 0.2536082343117003 + First Sample Start: 2024-10-14T08:31:16Z + Last Sample Start: 2024-10-14T08:32:08Z + Last Update Time: 2024-10-14T08:32:34Z + Memory Histogram: + Reference Timestamp: 2024-10-14T08:35:00Z + Ref: + Container Name: ferretdb + Vpa Object Name: ferretdb-autoscale + Total Samples Count: 2 + Version: v3 + Conditions: + Last Transition Time: 2024-10-14T08:32:29Z + Message: Successfully created FerretDBOpsRequest demo/frops-ferretdb-autoscale-5eo9wo + Observed Generation: 1 + Reason: CreateOpsRequest + Status: True + Type: CreateOpsRequest + Vpas: + Conditions: + Last Transition Time: 2024-10-14T08:31:34Z + Status: True + Type: RecommendationProvided + Recommendation: + Container Recommendations: + Container Name: ferretdb + 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: ferretdb-autoscale +Events: +``` +So, the `ferretdbautoscaler` resource is created successfully. + +you can see in the `Status.VPAs.Recommendation` section, that recommendation has been generated for our ferretdb. Our autoscaler operator continuously watches the recommendation generated and creates an `ferretdbopsrequest` based on the recommendations, if the ferretdb pods are needed to scaled up or down. + +Let's watch the `ferretdbopsrequest` in the demo namespace to see if any `ferretdbopsrequest` object is created. After some time you'll see that a `ferretdbopsrequest` will be created based on the recommendation. + +```bash +$ watch kubectl get ferretdbopsrequest -n demo +Every 2.0s: kubectl get ferretdbopsrequest -n demo +NAME TYPE STATUS AGE +frops-ferretdb-autoscale-5eo9wo VerticalScaling Progressing 10s +``` + +Let's wait for the ops request to become successful. + +```bash +$ watch kubectl get ferretdbopsrequest -n demo +Every 2.0s: kubectl get ferretdbopsrequest -n demo +NAME TYPE STATUS AGE +frops-ferretdb-autoscale-5eo9wo VerticalScaling Successful 31s +``` + +We can see from the above output that the `FerretDBOpsRequest` has succeeded. If we describe the `FerretDBOpsRequest` we will get an overview of the steps that were followed to scale the ferretdb. + +```bash +$ kubectl describe ferretdbopsrequest -n demo frops-ferretdb-autoscale-5eo9wo +Name: frops-ferretdb-autoscale-5eo9wo +Namespace: demo +Labels: app.kubernetes.io/component=database + app.kubernetes.io/instance=ferretdb-autoscale + app.kubernetes.io/managed-by=kubedb.com + app.kubernetes.io/name=ferretdbs.kubedb.com +Annotations: +API Version: ops.kubedb.com/v1alpha1 +Kind: FerretDBOpsRequest +Metadata: + Creation Timestamp: 2024-10-14T08:32:29Z + Generation: 1 + Owner References: + API Version: autoscaling.kubedb.com/v1alpha1 + Block Owner Deletion: true + Controller: true + Kind: FerretDBAutoscaler + Name: ferretdb-autoscale-ops + UID: 62387d58-1cd2-4cb6-9d97-91515531fcea + Resource Version: 11153 + UID: f14acbf1-bd46-4b93-9ee7-d944d9f1f8fd +Spec: + Apply: IfReady + Database Ref: + Name: ferretdb-autoscale + Type: VerticalScaling + Vertical Scaling: + Node: + Resources: + Limits: + Cpu: 400m + Memory: 400Mi + Requests: + Cpu: 400m + Memory: 400Mi +Status: + Conditions: + Last Transition Time: 2024-10-14T08:32:29Z + Message: FerretDB ops-request has started to vertically scaling the FerretDB nodes + Observed Generation: 1 + Reason: VerticalScaling + Status: True + Type: VerticalScaling + Last Transition Time: 2024-10-14T08:32:32Z + Message: Successfully paused database + Observed Generation: 1 + Reason: DatabasePauseSucceeded + Status: True + Type: DatabasePauseSucceeded + Last Transition Time: 2024-10-14T08:32:32Z + Message: Successfully updated PetSets Resources + Observed Generation: 1 + Reason: UpdatePetSets + Status: True + Type: UpdatePetSets + Last Transition Time: 2024-10-14T08:32:37Z + Message: get pod; ConditionStatus:True; PodName:ferretdb-autoscale-0 + Observed Generation: 1 + Status: True + Type: GetPod--ferretdb-autoscale-0 + Last Transition Time: 2024-10-14T08:32:37Z + Message: evict pod; ConditionStatus:True; PodName:ferretdb-autoscale-0 + Observed Generation: 1 + Status: True + Type: EvictPod--ferretdb-autoscale-0 + Last Transition Time: 2024-10-14T08:32:42Z + Message: check pod running; ConditionStatus:True; PodName:ferretdb-autoscale-0 + Observed Generation: 1 + Status: True + Type: CheckPodRunning--ferretdb-autoscale-0 + Last Transition Time: 2024-10-14T08:32:47Z + Message: Successfully Restarted Pods With Resources + Observed Generation: 1 + Reason: RestartPods + Status: True + Type: RestartPods + Last Transition Time: 2024-10-14T08:32:48Z + Message: Successfully completed the VerticalScaling for FerretDB + Observed Generation: 1 + Reason: Successful + Status: True + Type: Successful + Observed Generation: 1 + Phase: Successful +Events: + Type Reason Age From Message + ---- ------ ---- ---- ------- + Normal Starting 3m7s KubeDB Ops-manager Operator Start processing for FerretDBOpsRequest: demo/frops-ferretdb-autoscale-5eo9wo + Normal Starting 3m7s KubeDB Ops-manager Operator Pausing FerretDB database: demo/ferretdb-autoscale + Normal Successful 3m7s KubeDB Ops-manager Operator Successfully paused FerretDB database: demo/ferretdb-autoscale for FerretDBOpsRequest: frops-ferretdb-autoscale-5eo9wo + Normal UpdatePetSets 3m4s KubeDB Ops-manager Operator Successfully updated PetSets Resources + Warning get pod; ConditionStatus:True; PodName:ferretdb-autoscale-0 2m59s KubeDB Ops-manager Operator get pod; ConditionStatus:True; PodName:ferretdb-autoscale-0 + Warning evict pod; ConditionStatus:True; PodName:ferretdb-autoscale-0 2m59s KubeDB Ops-manager Operator evict pod; ConditionStatus:True; PodName:ferretdb-autoscale-0 + Warning check pod running; ConditionStatus:True; PodName:ferretdb-autoscale-0 2m54s KubeDB Ops-manager Operator check pod running; ConditionStatus:True; PodName:ferretdb-autoscale-0 + Normal RestartPods 2m49s KubeDB Ops-manager Operator Successfully Restarted Pods With Resources + Normal Starting 2m49s KubeDB Ops-manager Operator Resuming FerretDB database: demo/ferretdb-autoscale + Normal Successful 2m48s KubeDB Ops-manager Operator Successfully resumed FerretDB database: demo/ferretdb-autoscale for FerretDBOpsRequest: frops-ferretdb-autoscale-5eo9wo +``` + +Now, we are going to verify from the Pod, and the FerretDB yaml whether the resources of the ferretdb has updated to meet up the desired state, Let's check, + +```bash +$ kubectl get ferretdb -n demo ferretdb-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 FerretDB. + +## Cleaning Up + +To clean up the Kubernetes resources created by this tutorial, run: + +```bash +kubectl delete fr -n demo ferretdb-autoscale +kubectl delete ferretdbautoscaler -n demo ferretdb-autoscale-ops +``` \ No newline at end of file diff --git a/docs/guides/ferretdb/autoscaler/compute/overview.md b/docs/guides/ferretdb/autoscaler/compute/overview.md new file mode 100644 index 0000000000..04c9a98999 --- /dev/null +++ b/docs/guides/ferretdb/autoscaler/compute/overview.md @@ -0,0 +1,55 @@ +--- +title: FerretDB Compute Autoscaling Overview +menu: + docs_{{ .version }}: + identifier: fr-auto-scaling-overview + name: Overview + parent: fr-compute-auto-scaling + weight: 10 +menu_name: docs_{{ .version }} +section_menu_id: guides +--- + +> New to KubeDB? Please start [here](/docs/README.md). + +# FerretDB 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 `FerretdbAutoscaler` crd. + +## Before You Begin + +- You should be familiar with the following `KubeDB` concepts: + - [FerretDB](/docs/guides/ferretdb/concepts/ferretdb.md) + - [FerretDBAutoscaler](/docs/guides/ferretdb/concepts/autoscaler.md) + - [FerretDBOpsRequest](/docs/guides/ferretdb/concepts/opsrequest.md) + +## How Compute Autoscaling Works + +The following diagram shows how KubeDB Autoscaler operator autoscales the resources of `FerretDB`. Open the image in a new tab to see the enlarged version. + +
+  Compute Auto Scaling process of FerretDB +
Fig: Compute Auto Scaling process of FerretDB
+
+ +The Auto Scaling process consists of the following steps: + +1. At first, a user creates a `FerretDB` Custom Resource Object (CRO). + +2. `KubeDB` Provisioner operator watches the `FerretDB` CRO. + +3. When the operator finds a `FerretDB` CRO, it creates `PetSet` and related necessary stuff like secrets, services, etc. + +4. Then, in order to set up autoscaling of `FerretDB`, the user creates a `FerretDBAutoscaler` CRO with desired configuration. + +5. `KubeDB` Autoscaler operator watches the `FerretDBAutoscaler` 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 `FerretDBAutoscaler` CRO. + +7. If the generated recommendation doesn't match the current resources of the database, then `KubeDB` Autoscaler operator creates a `FerretDBOpsRequest` CRO to scale the ferretdb to match the recommendation generated. + +8. `KubeDB` Ops-manager operator watches the `FerretDBOpsRequest` CRO. + +9. Then the `KubeDB` Ops-manager operator will scale the ferretdb vertically as specified on the `FerretDBOpsRequest` CRO. + +In the next docs, we are going to show a step-by-step guide on Autoscaling of FerretDB using `FerretDBAutoscaler` CRD. diff --git a/docs/guides/ferretdb/concepts/_index.md b/docs/guides/ferretdb/concepts/_index.md new file mode 100644 index 0000000000..fb3967d8aa --- /dev/null +++ b/docs/guides/ferretdb/concepts/_index.md @@ -0,0 +1,10 @@ +--- +title: FerretDB Concepts +menu: + docs_{{ .version }}: + identifier: fr-concepts-ferretdb + name: Concepts + parent: fr-ferretdb-guides + weight: 15 +menu_name: docs_{{ .version }} +--- \ No newline at end of file diff --git a/docs/guides/ferretdb/concepts/appbinding.md b/docs/guides/ferretdb/concepts/appbinding.md new file mode 100644 index 0000000000..f835ad66da --- /dev/null +++ b/docs/guides/ferretdb/concepts/appbinding.md @@ -0,0 +1,147 @@ +--- +title: AppBinding CRD +menu: + docs_{{ .version }}: + identifier: fr-appbinding-concepts + name: AppBinding + parent: fr-concepts-ferretdb + weight: 20 +menu_name: docs_{{ .version }} +section_menu_id: guides +--- + +> New to KubeDB? Please start [here](/docs/README.md). + +# AppBinding + +## What is AppBinding + +An `AppBinding` is a Kubernetes `CustomResourceDefinition`(CRD) which points to an application using either its URL (usually for a non-Kubernetes resident service instance) or a Kubernetes service object (if self-hosted in a Kubernetes cluster), some optional parameters and a credential secret. To learn more about AppBinding and the problems it solves, please read this blog post: [The case for AppBinding](https://appscode.com/blog/post/the-case-for-appbinding). + +If you deploy a database using [KubeDB](https://kubedb.com/), `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. + +An `AppBinding` object created by `KubeDB` for PostgreSQL database is shown below, + +```yaml +apiVersion: appcatalog.appscode.com/v1alpha1 +kind: AppBinding +metadata: + labels: + app.kubernetes.io/component: database + app.kubernetes.io/instance: quick-postgres + app.kubernetes.io/managed-by: kubedb.com + app.kubernetes.io/name: postgreses.kubedb.com + name: quick-postgres + namespace: demo +spec: + appRef: + apiGroup: kubedb.com + kind: Postgres + name: quick-postgres + namespace: demo + clientConfig: + service: + name: quick-postgres + path: / + port: 5432 + query: sslmode=disable + scheme: postgresql + secret: + name: quick-postgres-auth + type: kubedb.com/postgres + version: "13.13" +``` + +Here, we are going to describe the sections of an `AppBinding` crd. + +### AppBinding `Spec` + +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. + +This field follows the following format: `/`. The above AppBinding is pointing to a `postgres` resource under `kubedb.com` group. + +Here, the variables are parsed as follows: + +| Variable | Usage | +| --------------------- | --------------------------------------------------------------------------------------------------------------------------------- | +| `TARGET_APP_GROUP` | Represents the application group where the respective app belongs (i.e: `kubedb.com`). | +| `TARGET_APP_RESOURCE` | Represents the resource under that application group that this appbinding represents (i.e: `postgres`). | +| `TARGET_APP_TYPE` | Represents the complete type of the application. It's simply `TARGET_APP_GROUP/TARGET_APP_RESOURCE` (i.e: `kubedb.com/postgres`). | + +#### spec.secret + +`spec.secret` specifies the name of the secret which contains the credentials that are required to access the database. This secret must be in the same namespace as the `AppBinding`. + +This secret must contain the following keys: + +PostgreSQL : + +| Key | Usage | +|------------|------------------------------------------------| +| `username` | Username of the target database. | +| `password` | Password for the user specified by `username`. | + +MySQL : + +| Key | Usage | +| ---------- | ---------------------------------------------- | +| `username` | Username of the target database. | +| `password` | Password for the user specified by `username`. | + +MongoDB : + +| Key | Usage | +| ---------- | ---------------------------------------------- | +| `username` | Username of the target database. | +| `password` | Password for the user specified by `username`. | + +Elasticsearch: + +| Key | Usage | +| ---------------- | ----------------------- | +| `ADMIN_USERNAME` | Admin username | +| `ADMIN_PASSWORD` | Password for admin user | + +#### spec.clientConfig + +`spec.clientConfig` defines how to communicate with the target database. You can use either a URL or a Kubernetes service to connect with the database. You don't have to specify both of them. + +You can configure following fields in `spec.clientConfig` section: + +- **spec.clientConfig.url** + + `spec.clientConfig.url` gives the location of the database, in standard URL form (i.e. `[scheme://]host:port/[path]`). This is particularly useful when the target database is running outside the Kubernetes cluster. If your database is running inside the cluster, use `spec.clientConfig.service` section instead. + + +> Note that, attempting to use a user or basic auth (e.g. `user:password@host:port`) is not allowed. Stash will insert them automatically from the respective secret. Fragments ("#...") and query parameters ("?...") are not allowed either. + +- **spec.clientConfig.service** + + If you are running the database inside the Kubernetes cluster, you can use Kubernetes service to connect with the database. You have to specify the following fields in `spec.clientConfig.service` section if you manually create an `AppBinding` object. + + - **name :** `name` indicates the name of the service that connects with the target database. + - **scheme :** `scheme` specifies the scheme (i.e. http, https) to use to connect with the database. + - **port :** `port` specifies the port where the target database is running. + +- **spec.clientConfig.insecureSkipTLSVerify** + + `spec.clientConfig.insecureSkipTLSVerify` is used to disable TLS certificate verification while connecting with the database. We strongly discourage to disable TLS verification during backup. You should provide the respective CA bundle through `spec.clientConfig.caBundle` field instead. + +- **spec.clientConfig.caBundle** + + `spec.clientConfig.caBundle` is a PEM encoded CA bundle which will be used to validate the serving certificate of the database. + +## Next Steps + +- Learn how to use KubeDB to manage various databases [here](/docs/guides/README.md). +- Want to hack on KubeDB? Check our [contribution guidelines](/docs/CONTRIBUTING.md). diff --git a/docs/guides/ferretdb/concepts/autoscaler.md b/docs/guides/ferretdb/concepts/autoscaler.md new file mode 100644 index 0000000000..589612ef35 --- /dev/null +++ b/docs/guides/ferretdb/concepts/autoscaler.md @@ -0,0 +1,73 @@ +--- +title: FerretDBAutoscaler CRD +menu: + docs_{{ .version }}: + identifier: fr-autoscaler-concepts + name: FerretDBAutoscaler + parent: fr-concepts-ferretdb + weight: 26 +menu_name: docs_{{ .version }} +section_menu_id: guides +--- + +> New to KubeDB? Please start [here](/docs/README.md). + +# FerretDBAutoscaler + +## What is FerretDBAutoscaler + +`FerretDBAutoscaler` is a Kubernetes `Custom Resource Definitions` (CRD). It provides a declarative configuration for autoscaling `FerretDB` compute resources of FerretDB components in a Kubernetes native way. + +## FerretDBAutoscaler CRD Specifications + +Like any official Kubernetes resource, a `FerretDBAutoscaler` has `TypeMeta`, `ObjectMeta`, `Spec` and `Status` sections. + +Here, some sample `FerretDBAutoscaler` CROs for autoscaling different components of ferretdb is given below: + +**Sample `FerretDBAutoscaler` for ferretdb:** + +```yaml +apiVersion: autoscaling.kubedb.com/v1alpha1 +kind: FerretDBAutoscaler +metadata: + name: ferretdb-auto-scale + namespace: demo +spec: + databaseRef: + name: ferretdb + compute: + ferretdb: + 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 `FerretDBAutoscaler` crd. + +A `FerretDBAutoscaler` object has the following fields in the `spec` section. + +### spec.databaseRef + +`spec.databaseRef` is a required field that point to the [FerretDB](/docs/guides/ferretdb/concepts/ferretdb.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 [FerretDB](/docs/guides/ferretdb/concepts/ferretdb.md) object. + +### spec.compute + +`spec.compute` specifies the autoscaling configuration for the compute resources i.e. cpu and memory of FerretDB components. This field consists of the following sub-field: + +- `trigger` indicates if compute autoscaling is enabled for this component of the ferretdb. 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/ferretdb/concepts/catalog.md b/docs/guides/ferretdb/concepts/catalog.md new file mode 100644 index 0000000000..e136e4d6d3 --- /dev/null +++ b/docs/guides/ferretdb/concepts/catalog.md @@ -0,0 +1,74 @@ +--- +title: FerretDBVersion CRD +menu: + docs_{{ .version }}: + identifier: fr-catalog-concepts + name: FerretDBVersion + parent: fr-concepts-ferretdb + weight: 15 +menu_name: docs_{{ .version }} +section_menu_id: guides +--- + +> New to KubeDB? Please start [here](/docs/README.md). + +# FerretDBVersion + +## What is FerretDBVersion + +`FerretDBVersion` is a Kubernetes `Custom Resource Definitions` (CRD). It provides a declarative configuration to specify the docker images to be used for [FerretDB](https://ferretdb.com/) server deployed with KubeDB in a Kubernetes native way. + +When you install KubeDB, a `FerretDBVersion` custom resource will be created automatically for every supported FerretDB release versions. You have to specify the name of `FerretDBVersion` crd in `spec.version` field of [FerretDB](/docs/guides/ferretdb/concepts/ferretdb.md) crd. Then, KubeDB will use the docker images specified in the `FerretDBVersion` crd to create your expected FerretDB instance. + +Using a separate crd for specifying respective docker image names allow us to modify the images independent of KubeDB operator. This will also allow the users to use a custom FerretDB image for their server. + +## FerretDBVersion Specification + +As with all other Kubernetes objects, a FerretDBVersion needs `apiVersion`, `kind`, and `metadata` fields. It also needs a `.spec` section. + +```yaml +apiVersion: catalog.kubedb.com/v1alpha1 +kind: FerretDBVersion +metadata: + name: 1.23.0 +spec: + db: + image: ghcr.io/appscode-images/ferretdb:1.23.0 + securityContext: + runAsUser: 1000 + version: 1.23.0 + deprecated: false +``` + +### metadata.name + +`metadata.name` is a required field that specifies the name of the `FerretDBVersion` crd. You have to specify this name in `spec.version` field of [FerretDB](/docs/guides/ferretdb/concepts/ferretdb.md) crd. + +We follow this convention for naming FerretDBVersion crd: + +- Name format: `{Original ferretdb image version}-{modification tag}` + +We plan to modify original FerretDB docker images to support additional features. Re-tagging the image with v1, v2 etc. modification tag help separating newer iterations from the older ones. An image with higher modification tag will have more features than the images with lower modification tag. Hence, it is recommended to use FerretDBVersion crd with higher modification tag to take advantage of the latest features. + +### spec.version + +`spec.version` is a required field that specifies the original version of FerretDB that has been used to build the docker image specified in `spec.server.image` field. + +### spec.deprecated + +`spec.deprecated` is an optional field that specifies whether the docker images specified here is supported by the current KubeDB operator. + +The default value of this field is `false`. If `spec.deprecated` is set `true`, KubeDB operator will not create the server and other respective resources for this version. + +### spec.ferretdb.image + +`spec.ferretdb.image` is a required field that specifies the docker image which will be used to create PetSet by KubeDB operator to create expected FerretDB server. + +### spec.securityContext + +`spec.securityContext` holds pod-level security attributes and common container settings for FerretDB pod. + +## Next Steps + +- Learn about FerretDB crd [here](/docs/guides/ferretdb/concepts/catalog.md). +- Deploy your first FerretDB server with KubeDB by following the guide [here](/docs/guides/ferretdb/quickstart/quickstart.md). \ No newline at end of file diff --git a/docs/guides/ferretdb/concepts/ferretdb.md b/docs/guides/ferretdb/concepts/ferretdb.md new file mode 100644 index 0000000000..deb8d2468b --- /dev/null +++ b/docs/guides/ferretdb/concepts/ferretdb.md @@ -0,0 +1,298 @@ +--- +title: FerretDB CRD +menu: + docs_{{ .version }}: + identifier: fr-ferretdb-concepts + name: FerretDB + parent: fr-concepts-ferretdb + weight: 10 +menu_name: docs_{{ .version }} +section_menu_id: guides +--- + +> New to KubeDB? Please start [here](/docs/README.md). + +# FerretDB + +## What is FerretDB + +`FerretDB` is a Kubernetes `Custom Resource Definitions` (CRD). It provides declarative configuration for [FerretDB](https://www.ferretdb.com/) in a Kubernetes native way. You only need to describe the desired configuration in a `FerretDB`object, and the KubeDB operator will create Kubernetes objects in the desired state for you. + +## FerretDB Spec + +As with all other Kubernetes objects, a FerretDB needs `apiVersion`, `kind`, and `metadata` fields. It also needs a `.spec` section. Below is an example FerretDB object. + +```yaml +apiVersion: kubedb.com/v1alpha2 +kind: FerretDB +metadata: + name: ferretdb + namespace: demo +spec: + version: "1.23.0" + replicas: 1 + healthChecker: + failureThreshold: 3 + periodSeconds: 20 + timeoutSeconds: 10 + authSecret: + name: ferretdb-auth + externallyManaged: false + backend: + postgresRef: + name: ha-postgres + namespace: demo + version: "13.13" + linkedDB: "ferretdb" + externallyManaged: false + sslMode: requireSSL + tls: + issuerRef: + apiGroup: cert-manager.io + name: ferretdb-ca-issuer + kind: Issuer + certificates: + - alias: server + subject: + organizations: + - kubedb:server + dnsNames: + - localhost + ipAddresses: + - "127.0.0.1" + monitor: + agent: prometheus.io/operator + prometheus: + serviceMonitor: + labels: + release: prometheus + interval: 10s + deletionPolicy: WipeOut + podTemplate: + spec: + containers: + - name: ferretdb + resources: + limits: + memory: 1Gi + requests: + cpu: 200m + memory: 256Mi + serviceTemplates: + - alias: primary + spec: + type: ClusterIP + ports: + - name: http + port: 9999 +``` + +### spec.version + +`spec.version` is a required field specifying the name of the [FerretDBVersion](/docs/guides/ferretdb/concepts/catalog.md) crd where the docker images are specified. Currently, when you install KubeDB, it creates the following `FerretDBVersion` resources, + +- `1.18.0`, `1.23.0` + +### spec.replicas + +`spec.replicas` the number of members in ferretdb replicaset. + +KubeDB uses `PodDisruptionBudget` to ensure that majority of these replicas are available during [voluntary disruptions](https://kubernetes.io/docs/concepts/workloads/pods/disruptions/#voluntary-and-involuntary-disruptions) so that quorum is maintained. + +### spec.healthChecker +It defines the attributes for the health checker. +- `spec.healthChecker.periodSeconds` specifies how often to perform the health check. +- `spec.healthChecker.timeoutSeconds` specifies the number of seconds after which the probe times out. +- `spec.healthChecker.failureThreshold` specifies minimum consecutive failures for the healthChecker to be considered failed. +- `spec.healthChecker.disableWriteCheck` specifies whether to disable the writeCheck or not. + +### spec.authSecret + +`spec.authSecret` is an optional field that points to a Secret used to hold credentials for `ferretdb`. If not set, KubeDB operator creates a new Secret `{ferretdb-object-name}-auth` for storing the password for `ferretdb` user for each FerretDB object. +As FerretDB use backend's authentication mechanisms till now, this secret is basically a copy of backend postgres. + +We can use this field in 3 mode. +1. Using an external secret. In this case, You need to create an auth secret first with required fields, then specify the secret name when creating the FerretDB object using `spec.authSecret.name` & set `spec.authSecret.externallyManaged` to true. +```yaml +authSecret: + name: + externallyManaged: true +``` + +2. Specifying the secret name only. In this case, You need to specify the secret name when creating the FerretDB object using `spec.authSecret.name`. `externallyManaged` is by default false. +```yaml +authSecret: + name: +``` + +3. Let KubeDB do everything for you. In this case, no work for you. + +AuthSecret contains a `user` key and a `password` key which contains the `username` and `password` respectively for `ferretdb` user. + +Example: + +```bash +$ kubectl create secret generic ferretdb-auth -n demo \ +--from-literal=username=jhon \ +--from-literal=password=O9xE1mZZDAdBTbrV +secret "ferretdb-auth" created +``` + +```yaml +apiVersion: v1 +data: + password: "O9xE1mZZDAdBTbrV" + username: "jhon" +kind: Secret +metadata: + name: ferretdb-auth + namespace: demo +type: Opaque +``` + +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). + +### spec.backend + +- `spec.backend.externallyManaged` represents how the backend will be managed. If its false, KubeDB will automatically create a KubeDB managed postgres. Otherwise you need refer a [AppBinding](/docs/guides/ferretdb/concepts/appbinding.md) name and namespace which represents information for external Postgres. +- `spec.backend.postgresRef` is a required field that points to the `appbinding` associated with the backend postgres. If the postgres is KubeDB managed an [AppBinding](/docs/guides/ferretdb/concepts/appbinding.md) will be created automatically upon creating the postgres. If the postgres is not KubeDB managed then you need to create an appbinding yourself. `spec.backend.postgresRef` takes the name (`spec.backend.postgresRef.Name`) of the appbinding and the namespace (`spec.backend.postgresRef.Namespace`) where the appbinding is created. +- `spec.backend.version` represents the version of backend postgres +- `spec.backend.linkedDB` represents in which database of backend postgres will be used by FerretDB to store data + +### spec.sslMode + +Enables TLS/SSL or mixed TLS/SSL used for all network connections. The value of [`sslMode`](https://docs.ferretdb.io/security/tls-connections/) field can be one of the following: + +| Value | Description | +| :----------: | :----------------------------------------------------------------------------------------------------------------------------- | +| `disabled` | The server does not use TLS/SSL. | +| `requireSSL` | The server uses and accepts only TLS/SSL encrypted connections. | + +### spec.tls + +`spec.tls` specifies the TLS/SSL configurations for the FerretDB. KubeDB uses [cert-manager](https://cert-manager.io/) v1 api to provision and manage TLS certificates. + +The following fields are configurable in the `spec.tls` section: + +- `issuerRef` is a reference to the `Issuer` or `ClusterIssuer` CR of [cert-manager](https://cert-manager.io/docs/concepts/issuer/) that will be used by `KubeDB` to generate necessary certificates. + + - `apiGroup` is the group name of the resource that is being referenced. Currently, the only supported value is `cert-manager.io`. + - `kind` is the type of resource that is being referenced. KubeDB supports both `Issuer` and `ClusterIssuer` as values for this field. + - `name` is the name of the resource (`Issuer` or `ClusterIssuer`) being referenced. + +- `certificates` (optional) are a list of certificates used to configure the server and/or client certificate. It has the following fields: + - `alias` represents the identifier of the certificate. It has the following possible value: + - `server` is used for server certificate identification. + - `client` is used for client certificate identification. + - `metrics-exporter` is used for metrics exporter certificate identification. + - `secretName` (optional) specifies the k8s secret name that holds the certificates. + > This field is optional. If the user does not specify this field, the default secret name will be created in the following format: `--cert`. + + - `subject` (optional) specifies an `X.509` distinguished name. It has the following possible field, + - `organizations` (optional) are the list of different organization names to be used on the Certificate. + - `organizationalUnits` (optional) are the list of different organization unit name to be used on the Certificate. + - `countries` (optional) are the list of country names to be used on the Certificate. + - `localities` (optional) are the list of locality names to be used on the Certificate. + - `provinces` (optional) are the list of province names to be used on the Certificate. + - `streetAddresses` (optional) are the list of a street address to be used on the Certificate. + - `postalCodes` (optional) are the list of postal code to be used on the Certificate. + - `serialNumber` (optional) is a serial number to be used on the Certificate. + You can find more details from [Here](https://golang.org/pkg/crypto/x509/pkix/#Name) + - `duration` (optional) is the period during which the certificate is valid. + - `renewBefore` (optional) is a specifiable time before expiration duration. + - `dnsNames` (optional) is a list of subject alt names to be used in the Certificate. + - `ipAddresses` (optional) is a list of IP addresses to be used in the Certificate. + - `uris` (optional) is a list of URI Subject Alternative Names to be set in the Certificate. + - `emailAddresses` (optional) is a list of email Subject Alternative Names to be set in the Certificate. + - `privateKey` (optional) specifies options to control private keys used for the Certificate. + - `encoding` (optional) is the private key cryptography standards (PKCS) encoding for this certificate's private key to be encoded in. If provided, allowed values are "pkcs1" and "pkcs8" standing for PKCS#1 and PKCS#8, respectively. It defaults to PKCS#1 if not specified. + +### spec.monitor + +FerretDB managed by KubeDB can be monitored with builtin-Prometheus and Prometheus operator out-of-the-box. To learn more, + +- [Monitor FerretDB with builtin Prometheus](/docs/guides/ferretdb/monitoring/using-builtin-prometheus.md) +- [Monitor FerretDB with Prometheus operator](/docs/guides/ferretdb/monitoring/using-prometheus-operator.md) + +### spec.deletionPolicy + +`deletionPolicy` gives flexibility whether to `nullify`(reject) the delete operation of `FerretDB` CR or which resources KubeDB should keep or delete when you delete `FerretDB` CR. KubeDB provides following four deletion policies: + +- DoNotTerminate +- Delete +- WipeOut (`Default`) + +When `deletionPolicy` is `DoNotTerminate`, KubeDB takes advantage of `ValidationWebhook` feature in Kubernetes 1.9.0 or later clusters to implement `DoNotTerminate` feature. If admission webhook is enabled, `DoNotTerminate` prevents users from deleting the database as long as the `spec.deletionPolicy` is set to `DoNotTerminate`. + +Following table show what KubeDB does when you delete FerretDB CR for different deletion policies, + +| Behavior | DoNotTerminate | Delete | WipeOut | +|---------------------------| :------------: |:------------:| :------: | +| 1. Block Delete operation | ✓ | ✗ | ✗ | +| 2. Delete PetSet | ✗ | ✓ | ✓ | +| 3. Delete Services | ✗ | ✓ | ✓ | +| 4. Delete Secrets | ✗ | ✗ | ✓ | + +If you don't specify `spec.deletionPolicy` KubeDB uses `Delete` deletion policy by default. + +### spec.podTemplate + +KubeDB allows providing a template for pod through `spec.podTemplate`. KubeDB operator will pass the information provided in `spec.podTemplate` to the PetSet created for FerretDB. + +KubeDB accept following fields to set in `spec.podTemplate:` + +- metadata: + - annotations (pod's annotation) + - labels (pod's labels) +- controller: + - annotations (statefulset's annotation) + - labels (statefulset's labels) +- spec: + - volumes + - initContainers + - containers + - imagePullSecrets + - nodeSelector + - affinity + - serviceAccountName + - schedulerName + - tolerations + - priorityClassName + - priority + - securityContext + - livenessProbe + - readinessProbe + - lifecycle + +You can check out the full list [here](https://github.com/kmodules/offshoot-api/blob/39bf8b2/api/v2/types.go#L44-L279). Uses of some field of `spec.podTemplate` is described below, + +#### spec.podTemplate.spec.nodeSelector + +`spec.podTemplate.spec.nodeSelector` is an optional field that specifies a map of key-value pairs. For the pod to be eligible to run on a node, the node must have each of the indicated key-value pairs as labels (it can have additional labels as well). To learn more, see [here](https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector) . + +#### spec.podTemplate.spec.resources + +`spec.podTemplate.spec.resources` is an optional field. This can be used to request compute resources required by the database pods. To learn more, visit [here](http://kubernetes.io/docs/user-guide/compute-resources/). + +### spec.serviceTemplates + +You can also provide template for the services created by KubeDB operator for Kafka cluster through `spec.serviceTemplates`. This will allow you to set the type and other properties of the services. + +KubeDB allows following fields to set in `spec.serviceTemplates`: +- `alias` represents the identifier of the service. It has the following possible value: + - `stats` is used for the exporter service identification. +- metadata: + - labels + - annotations +- spec: + - type + - ports + - clusterIP + - externalIPs + - loadBalancerIP + - loadBalancerSourceRanges + - externalTrafficPolicy + - healthCheckNodePort + - sessionAffinityConfig + +See [here](https://github.com/kmodules/offshoot-api/blob/kubernetes-1.21.1/api/v1/types.go#L237) to understand these fields in detail. \ No newline at end of file diff --git a/docs/guides/ferretdb/concepts/opsrequest.md b/docs/guides/ferretdb/concepts/opsrequest.md new file mode 100644 index 0000000000..6deb7e1ce9 --- /dev/null +++ b/docs/guides/ferretdb/concepts/opsrequest.md @@ -0,0 +1,258 @@ +--- +title: FerretDBOpsRequests CRD +menu: + docs_{{ .version }}: + identifier: fr-opsrequest-concepts + name: FerretDBOpsRequest + parent: fr-concepts-ferretdb + weight: 25 +menu_name: docs_{{ .version }} +section_menu_id: guides +--- + +> New to KubeDB? Please start [here](/docs/README.md). + +# FerretDBOpsRequest + +## What is FerretDBOpsRequest + +`FerretDBOpsRequest` is a Kubernetes `Custom Resource Definitions` (CRD). It provides a declarative configuration for `FerretDB` administrative operations like version updating, horizontal scaling, vertical scaling etc. in a Kubernetes native way. + +## FerretDBOpsRequest CRD Specifications + +Like any official Kubernetes resource, a `FerretDBOpsRequest` has `TypeMeta`, `ObjectMeta`, `Spec` and `Status` sections. + +Here, some sample `FerretDBOpsRequest` CRs for different administrative operations is given below: + +**Sample `FerretDBOpsRequest` for updating version:** + +```yaml +apiVersion: ops.kubedb.com/v1alpha1 +kind: FerretDBOpsRequest +metadata: + name: ferretdb-version-update + namespace: demo +spec: + type: UpdateVersion + databaseRef: + name: ferretdb + updateVersion: + targetVersion: 1.23.0 +``` + +**Sample `FerretDBOpsRequest` Objects for Horizontal Scaling:** + +```yaml +apiVersion: ops.kubedb.com/v1alpha1 +kind: FerretDBOpsRequest +metadata: + name: ferretdb-horizontal-scale + namespace: demo +spec: + type: HorizontalScaling + databaseRef: + name: ferretdb + horizontalScaling: + node: 3 +``` + +**Sample `FerretDBOpsRequest` Objects for Vertical Scaling:** + +```yaml +apiVersion: ops.kubedb.com/v1alpha1 +kind: FerretDBOpsRequest +metadata: + name: ferretdb-vertical-scale + namespace: demo +spec: + type: VerticalScaling + databaseRef: + name: ferretdb + verticalScaling: + node: + resources: + requests: + memory: "1200Mi" + cpu: "0.7" + limits: + memory: "1200Mi" + cpu: "0.7" +``` + +**Sample `FerretDBOpsRequest` Objects for Reconfiguring TLS:** + +```yaml +apiVersion: ops.kubedb.com/v1alpha1 +kind: FerretDBOpsRequest +metadata: + name: tls + namespace: demo +spec: + type: ReconfigureTLS + databaseRef: + name: ferretdb + tls: + sslMode: requireSSL + issuerRef: + name: ferretdb-ca-issuer + kind: Issuer + apiGroup: "cert-manager.io" + certificates: + - alias: client + subject: + organizations: + - kubedb + organizationalUnits: + - client +``` + +```yaml +apiVersion: ops.kubedb.com/v1alpha1 +kind: FerretDBOpsRequest +metadata: + name: tls + namespace: demo +spec: + type: ReconfigureTLS + databaseRef: + name: ferretdb + tls: + rotateCertificates: true +``` + +```yaml +apiVersion: ops.kubedb.com/v1alpha1 +kind: FerretDBOpsRequest +metadata: + name: tls + namespace: demo +spec: + type: ReconfigureTLS + databaseRef: + name: ferretdb + tls: + remove: true +``` + +Here, we are going to describe the various sections of a `FerretDBOpsRequest` crd. + +A `FerretDBOpsRequest` object has the following fields in the `spec` section. + +### spec.databaseRef + +`spec.databaseRef` is a required field that point to the [FerretDB](/docs/guides/ferretdb/concepts/ferretdb.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 [FerretDB](/docs/guides/ferretdb/concepts/ferretdb.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 `FerretDBOpsRequest`. + +- `Upgrade` / `UpdateVersion` +- `HorizontalScaling` +- `VerticalScaling` +- `ReconfigureTLS` +- `Restart` + +> You can perform only one type of operation on a single `FerretDBOpsRequest` CR. For example, if you want to update your database and scale up its replica then you have to create two separate `FerretDBOpsRequest`. At first, you have to create a `FerretDBOpsRequest` for updating. Once it is completed, then you can create another `FerretDBOpsRequest` for scaling. + +### spec.updateVersion + +If you want to update your FerretDB 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 [FerretDBVersion](/docs/guides/ferretdb/concepts/catalog.md) CR that contains the FerretDB version information where you want to update. + + +### spec.horizontalScaling + +If you want to scale-up or scale-down your FerretDB cluster or different components of it, you have to specify `spec.horizontalScaling` section. This field consists of the following sub-field: + +- `spec.horizontalScaling.node` indicates the desired number of pods for FerretDB 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.node` field. Similarly, if you want to remove one pod from the cluster, you have to specify 3 in `spec.horizontalScaling.node` field. + +### spec.verticalScaling + +`spec.verticalScaling` is a required field specifying the information of `FerretDB` resources like `cpu`, `memory` etc. that will be scaled. This field consists of the following sub-fields: + +- `spec.verticalScaling.node` indicates the desired resources for PetSet of FerretDB 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.tls + +If you want to reconfigure the TLS configuration of your ferretdb 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/ferretdb/concepts/ferretdb.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 obsRequest 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. + + +### FerretDBOpsRequest `Status` + +`.status` describes the current state and progress of a `FerretDBOpsRequest` operation. It has the following fields: + +### status.phase + +`status.phase` indicates the overall phase of the operation for this `FerretDBOpsRequest`. It can have the following three values: + +| Phase | Meaning | +|-------------|------------------------------------------------------------------------------------| +| Successful | KubeDB has successfully performed the operation requested in the FerretDBOpsRequest | +| Progressing | KubeDB has started the execution of the applied FerretDBOpsRequest | +| Failed | KubeDB has failed the operation requested in the FerretDBOpsRequest | +| Denied | KubeDB has denied the operation requested in the FerretDBOpsRequest | +| Skipped | KubeDB has skipped the operation requested in the FerretDBOpsRequest | + +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 `FerretDBOpsRequest` controller. + +### status.conditions + +`status.conditions` is an array that specifies the conditions of different steps of `FerretDBOpsRequest` processing. Each condition entry has the following fields: + +- `types` specifies the type of the condition. FerretDBOpsRequest 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 FerretDB 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/ferretdb/monitoring/_index.md b/docs/guides/ferretdb/monitoring/_index.md new file mode 100644 index 0000000000..40795496c4 --- /dev/null +++ b/docs/guides/ferretdb/monitoring/_index.md @@ -0,0 +1,10 @@ +--- +title: Monitoring FerretDB +menu: + docs_{{ .version }}: + identifier: fr-monitoring-ferretdb + name: Monitoring + parent: fr-ferretdb-guides + weight: 50 +menu_name: docs_{{ .version }} +--- \ No newline at end of file diff --git a/docs/guides/ferretdb/monitoring/overview.md b/docs/guides/ferretdb/monitoring/overview.md new file mode 100644 index 0000000000..bcbb7441d7 --- /dev/null +++ b/docs/guides/ferretdb/monitoring/overview.md @@ -0,0 +1,91 @@ +--- +title: FerretDB Monitoring Overview +description: FerretDB Monitoring Overview +menu: + docs_{{ .version }}: + identifier: fr-monitoring-overview + name: Overview + parent: fr-monitoring-ferretdb + weight: 10 +menu_name: docs_{{ .version }} +section_menu_id: guides +--- + +> New to KubeDB? Please start [here](/docs/README.md). + +# Monitoring FerretDB 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 FerretDB 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/v1alpha2 +kind: FerretDB +metadata: + name: sample-ferretdb + namespace: databases +spec: + version: "1.23.0" + deletionPolicy: WipeOut + backend: + externallyManaged: false + storage: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 500Mi + 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 FerretDB database with KubeDB using [builtin-Prometheus](/docs/guides/ferretdb/monitoring/using-builtin-prometheus.md) +- Learn how to monitor FerretDB database with KubeDB using [Prometheus operator](/docs/guides/ferretdb/monitoring/using-prometheus-operator.md). + diff --git a/docs/guides/ferretdb/monitoring/using-builtin-prometheus.md b/docs/guides/ferretdb/monitoring/using-builtin-prometheus.md new file mode 100644 index 0000000000..203f4a8d10 --- /dev/null +++ b/docs/guides/ferretdb/monitoring/using-builtin-prometheus.md @@ -0,0 +1,366 @@ +--- +title: Monitor FerretDB using Builtin Prometheus Discovery +menu: + docs_{{ .version }}: + identifier: fr-using-builtin-prometheus-monitoring + name: Builtin Prometheus + parent: fr-monitoring-ferretdb + weight: 20 +menu_name: docs_{{ .version }} +section_menu_id: guides +--- + +> New to KubeDB? Please start [here](/docs/README.md). + +# Monitoring FerretDB with builtin Prometheus + +This tutorial will show you how to monitor FerretDB 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/ferretdb/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/ferretdb](https://github.com/kubedb/docs/tree/{{< param "info.version" >}}/docs/examples/ferretdb) folder in GitHub repository [kubedb/docs](https://github.com/kubedb/docs). + +## Deploy FerretDB with Monitoring Enabled + +At first, let's deploy a FerretDB with monitoring enabled. Below is the FerretDB object that we are going to create. + +```yaml +apiVersion: kubedb.com/v1alpha2 +kind: FerretDB +metadata: + name: builtin-prom-fr + namespace: demo +spec: + version: "1.23.0" + storage: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 500Mi + backend: + externallyManaged: false + deletionPolicy: WipeOut + replicas: 2 + 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 FerretDB crd we have shown above. + +```bash +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/ferretdb/monitoring/builtin-prom-fr.yaml +ferretdb.kubedb.com/builtin-prom-fr created +``` + +Now, wait for the database to go into `Running` state. + +```bash +$ kubectl get fr -n demo builtin-prom-fr +NAME NAMESPACE VERSION STATUS AGE +builtin-prom-fr demo 1.18.0 Ready 2m41s +``` + +KubeDB will create a separate stats service with name `{FerretDB crd name}-stats` for monitoring purpose. + +```bash +$ kubectl get svc -n demo --selector="app.kubernetes.io/instance=builtin-prom-fr" +NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE +builtin-prom-fr ClusterIP 10.96.10.31 27017/TCP 3m14s +builtin-prom-fr-stats ClusterIP 10.96.216.137 56790/TCP 3m14s +``` + +Here, `builtin-prom-fr-stats` service has been created for monitoring purpose. Let's describe the service. + +```bash +$ kubectl describe svc -n demo builtin-prom-fr-stats +Name: builtin-prom-fr-stats +Namespace: demo +Labels: app.kubernetes.io/component=database + app.kubernetes.io/instance=builtin-prom-fr + app.kubernetes.io/managed-by=kubedb.com + app.kubernetes.io/name=ferretdbs.kubedb.com + kubedb.com/role=stats +Annotations: monitoring.appscode.com/agent: prometheus.io/builtin + prometheus.io/path: /debug/metrics + prometheus.io/port: 56790 + prometheus.io/scrape: true +Selector: app.kubernetes.io/instance=builtin-prom-fr,app.kubernetes.io/managed-by=kubedb.com,app.kubernetes.io/name=ferretdbs.kubedb.com +Type: ClusterIP +IP Family Policy: SingleStack +IP Families: IPv4 +IP: 10.96.216.137 +IPs: 10.96.216.137 +Port: metrics 56790/TCP +TargetPort: metrics/TCP +Endpoints: 10.244.0.56:8080,10.244.0.57:8080 +Session Affinity: None +Events: +``` + +You can see that the service contains following annotations. + +```bash +prometheus.io/path: /debug/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 +``` + +### 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-vkrfz 1/1 Running 0 21s +``` + +Now, run following command on a separate terminal to forward 9090 port of `prometheus-d64b668fb-vkrfz` pod, + +```bash +$ kubectl port-forward -n monitoring prometheus-d64b668fb-vkrfz 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-fr-stats` service as one of the targets. + +

+   +

+ +Check the labels. These labels confirm that the metrics are coming from `FerretDB` database `builtin-prom-fr` through stats service `builtin-prom-fr-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 fr/builtin-prom-fr + +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 FerretDB database with KubeDB using [out-of-the-box prometheus-Operator](/docs/guides/ferretdb/monitoring/using-prometheus-operator.md). +- Detail concepts of [FerretDB object](/docs/guides/ferretdb/concepts/ferretdb.md). +- Detail concepts of [FerretDBVersion object](/docs/guides/ferretdb/concepts/catalog.md). +- Want to hack on KubeDB? Check our [contribution guidelines](/docs/CONTRIBUTING.md). diff --git a/docs/guides/ferretdb/monitoring/using-prometheus-operator.md b/docs/guides/ferretdb/monitoring/using-prometheus-operator.md new file mode 100644 index 0000000000..861373823a --- /dev/null +++ b/docs/guides/ferretdb/monitoring/using-prometheus-operator.md @@ -0,0 +1,362 @@ +--- +title: Monitor FerretDB using Prometheus Operator +menu: + docs_{{ .version }}: + identifier: fr-using-prometheus-operator-monitoring + name: Prometheus Operator + parent: fr-monitoring-ferretdb + weight: 15 +menu_name: docs_{{ .version }} +section_menu_id: guides +--- + +> New to KubeDB? Please start [here](/docs/README.md). + +# Monitoring FerretDB 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 FerretDB database 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/ferretdb/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/ferretdb](https://github.com/kubedb/docs/tree/{{< param "info.version" >}}/docs/examples/ferretdb) 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 FerretDB 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 DESIRED READY RECONCILED AVAILABLE AGE +monitoring prometheus-kube-prometheus-prometheus v2.54.1 1 1 True True 13m +``` + +> 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-10-14T17:17:25Z" + 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: 65.2.0 + chart: kube-prometheus-stack-65.2.0 + heritage: Helm + release: prometheus + name: prometheus-kube-prometheus-prometheus + namespace: monitoring + resourceVersion: "58118" + uid: b1bf237b-2fdc-459c-b92f-e087a1119f33 +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.54.1 + 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.54.1 + walCompression: true +status: + availableReplicas: 1 + conditions: + - lastTransitionTime: "2024-10-14T17:27:17Z" + message: "" + observedGeneration: 1 + reason: "" + status: "True" + type: Available + - lastTransitionTime: "2024-10-14T17:27:17Z" + 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 FerretDB crd. + +## Deploy FerretDB with Monitoring Enabled + +At first, let's deploy an FerretDB database with monitoring enabled. Below is the FerretDB object that we are going to create. + +```yaml +apiVersion: kubedb.com/v1alpha2 +kind: FerretDB +metadata: + name: coreos-prom-fr + namespace: demo +spec: + version: "1.18.0" + storage: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 500Mi + backend: + externallyManaged: false + deletionPolicy: WipeOut + replicas: 2 + 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 FerretDB object that we have shown above, + +```bash +$ kubectl create -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/ferretdb/monitoring/coreos-prom-fr.yaml +ferretdb.kubedb.com/coreos-prom-fr created +``` + +Now, wait for the database to go into `Running` state. + +```bash +$ kubectl get fr -n demo coreos-prom-fr +NAME NAMESPACE VERSION STATUS AGE +coreos-prom-fr demo 1.18.0 Ready 111s +``` + +KubeDB will create a separate stats service with name `{FerretDB crd name}-stats` for monitoring purpose. + +```bash +$ kubectl get svc -n demo --selector="app.kubernetes.io/instance=coreos-prom-fr" +NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE +coreos-prom-fr ClusterIP 10.96.234.253 27017/TCP 2m16s +coreos-prom-fr-stats ClusterIP 10.96.27.143 56790/TCP 2m16s +``` + +Here, `coreos-prom-fr-stats` service has been created for monitoring purpose. + +Let's describe this stats service. + +```bash +$ kubectl describe svc -n demo coreos-prom-fr-stats +``` +```yaml +Name: coreos-prom-fr-stats +Namespace: demo +Labels: app.kubernetes.io/component=database + app.kubernetes.io/instance=coreos-prom-fr + app.kubernetes.io/managed-by=kubedb.com + app.kubernetes.io/name=ferretdbs.kubedb.com + kubedb.com/role=stats +Annotations: monitoring.appscode.com/agent: prometheus.io/operator +Selector: app.kubernetes.io/instance=coreos-prom-fr,app.kubernetes.io/managed-by=kubedb.com,app.kubernetes.io/name=ferretdbs.kubedb.com +Type: ClusterIP +IP Family Policy: SingleStack +IP Families: IPv4 +IP: 10.96.27.143 +IPs: 10.96.27.143 +Port: metrics 56790/TCP +TargetPort: metrics/TCP +Endpoints: 10.244.0.59:8080,10.244.0.60:8080 +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-fr-stats` service. Verify that the `ServiceMonitor` crd has been created. + +```bash +$ kubectl get servicemonitor -n demo +NAME AGE +coreos-prom-fr-pg-backend-stats 3m33s +coreos-prom-fr-stats 2m24s +``` + +> If backend Postgres is managed by KubeDB, KubeDB operator will also enable monitoring to backend Postgres. That's why `coreos-prom-fr-pg-backend-stats` `ServiceMonitor` also created. +To look at the more details of KubeDB managed Postgres monitoring, you can look at [this documentation](/docs/guides/postgres/monitoring/using-prometheus-operator.md). + +Let's verify that the `ServiceMonitor` has the label that we had specified in `spec.monitor` section of FerretDB crd. + +```bash +$ kubectl get servicemonitor -n demo coreos-prom-fr-stats -o yaml +``` +```yaml +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + creationTimestamp: "2024-10-14T17:35:38Z" + generation: 1 + labels: + app.kubernetes.io/component: database + app.kubernetes.io/instance: coreos-prom-fr + app.kubernetes.io/managed-by: kubedb.com + app.kubernetes.io/name: ferretdbs.kubedb.com + release: prometheus + name: coreos-prom-fr-stats + namespace: demo + ownerReferences: + - apiVersion: v1 + blockOwnerDeletion: true + controller: true + kind: Service + name: coreos-prom-fr-stats + uid: d0811d68-6e31-4357-b35a-8a7793ab4918 + resourceVersion: "59094" + uid: cfa29869-8000-44fe-bc9b-e7e78b08da36 +spec: + endpoints: + - honorLabels: true + interval: 10s + path: /debug/metrics + port: metrics + namespaceSelector: + matchNames: + - demo + selector: + matchLabels: + app.kubernetes.io/component: database + app.kubernetes.io/instance: coreos-prom-fr + app.kubernetes.io/managed-by: kubedb.com + app.kubernetes.io/name: ferretdbs.kubedb.com + kubedb.com/role: stats +``` + +Notice that the `ServiceMonitor` has label `release: prometheus` that we had specified in FerretDB crd. + +Also notice that the `ServiceMonitor` has selector which match the labels we have seen in the `coreos-prom-fr-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 0 27m +``` + +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-fr-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 fr/coreos-prom-fr +kubectl delete ns demo +``` + +## Next Steps + +- Monitor your FerretDB database with KubeDB using [out-of-the-box builtin-Prometheus](/docs/guides/ferretdb/monitoring/using-builtin-prometheus.md). +- Detail concepts of [FerretDB object](/docs/guides/ferretdb/concepts/ferretdb.md). +- Detail concepts of [FerretDBVersion object](/docs/guides/ferretdb/concepts/catalog.md). +- Want to hack on KubeDB? Check our [contribution guidelines](/docs/CONTRIBUTING.md). diff --git a/docs/guides/ferretdb/quickstart/quickstart.md b/docs/guides/ferretdb/quickstart/quickstart.md index d1b66499ef..1b9ae049cc 100644 --- a/docs/guides/ferretdb/quickstart/quickstart.md +++ b/docs/guides/ferretdb/quickstart/quickstart.md @@ -136,7 +136,7 @@ Metadata: UID: 73247297-139b-4dfe-8f9d-9baf2b092364 Spec: Auth Secret: - Name: ferret-pg-backend-auth + Name: ferret-auth Backend: Externally Managed: false Linked DB: ferretdb @@ -222,9 +222,6 @@ Status: $ kubectl get petset -n demo NAME READY AGE ferret 1/1 29m - -$ kubectl get petset -n demo -NAME READY AGE ferret-pg-backend 2/2 30m ferret-pg-backend-arbiter 1/1 29m @@ -273,7 +270,7 @@ metadata: uid: 73247297-139b-4dfe-8f9d-9baf2b092364 spec: authSecret: - name: ferret-pg-backend-auth + name: ferret-auth backend: externallyManaged: false linkedDB: ferretdb @@ -355,16 +352,16 @@ status: phase: Ready ``` -Please note that KubeDB operator has created a new Secret called `ferret-pg-backend-auth` *(format: {ferretdb-object-name}-backend-auth)* for storing the password for `postgres` superuser. This secret contains a `username` key which contains the *username* for FerretDB superuser and a `password` key which contains the *password* for FerretDB superuser. +Please note that KubeDB operator has created a new Secret called `ferret-auth` *(format: {ferretdb-object-name}-auth)* for storing the password for `postgres` superuser. This secret contains a `username` key which contains the *username* for FerretDB superuser and a `password` key which contains the *password* for FerretDB superuser. If you want to use custom or existing secret please specify that when creating the FerretDB object using `spec.authSecret.name`. While creating this secret manually, make sure the secret contains these two keys containing data `username` and `password`. For more details, please see [here](/docs/guides/mongodb/concepts/mongodb.md#specauthsecret). Now, you can connect to this database by port-forwarding primary service `ferret` and connecting with [mongo-shell](https://www.mongodb.com/try/download/shell) locally ```bash -$ kubectl get secrets -n demo ferret-pg-backend-auth -o jsonpath='{.data.\username}' | base64 -d +$ kubectl get secrets -n demo ferret-auth -o jsonpath='{.data.\username}' | base64 -d postgres -$ kubectl get secrets -n demo ferret-pg-backend-auth -o jsonpath='{.data.\\password}' | base64 -d +$ kubectl get secrets -n demo ferret-auth -o jsonpath='{.data.\\password}' | base64 -d UxV5a35kURSFE(;5 $ kubectl port-forward svc/ferret -n demo 27017 diff --git a/docs/guides/ferretdb/reconfigure-tls/_index.md b/docs/guides/ferretdb/reconfigure-tls/_index.md new file mode 100644 index 0000000000..071cb2eb3a --- /dev/null +++ b/docs/guides/ferretdb/reconfigure-tls/_index.md @@ -0,0 +1,10 @@ +--- +title: Reconfigure FerretDB TLS/SSL +menu: + docs_{{ .version }}: + identifier: fr-reconfigure-tls + name: Reconfigure TLS/SSL + parent: fr-ferretdb-guides + weight: 46 +menu_name: docs_{{ .version }} +--- diff --git a/docs/guides/ferretdb/reconfigure-tls/overview.md b/docs/guides/ferretdb/reconfigure-tls/overview.md new file mode 100644 index 0000000000..d47fe6b2f4 --- /dev/null +++ b/docs/guides/ferretdb/reconfigure-tls/overview.md @@ -0,0 +1,54 @@ +--- +title: Reconfiguring TLS of FerretDB +menu: + docs_{{ .version }}: + identifier: fr-reconfigure-tls-overview + name: Overview + parent: fr-reconfigure-tls + weight: 10 +menu_name: docs_{{ .version }} +section_menu_id: guides +--- + +> New to KubeDB? Please start [here](/docs/README.md). + +# Reconfiguring TLS of FerretDB + +This guide will give an overview on how KubeDB Ops-manager operator reconfigures TLS configuration i.e. add TLS, remove TLS, update issuer/cluster issuer or Certificates and rotate the certificates of a `FerretDB`. + +## Before You Begin + +- You should be familiar with the following `KubeDB` concepts: + - [FerretDB](/docs/guides/ferretdb/concepts/ferretdb.md) + - [FerretDBOpsRequest](/docs/guides/ferretdb/concepts/opsrequest.md) + +## How Reconfiguring FerretDB TLS Configuration Process Works + +The following diagram shows how KubeDB Ops-manager operator reconfigures TLS of a `FerretDB`. Open the image in a new tab to see the enlarged version. + +
+  Reconfiguring TLS process of FerretDB +
Fig: Reconfiguring TLS process of FerretDB
+
+ +The Reconfiguring FerretDB TLS process consists of the following steps: + +1. At first, a user creates a `FerretDB` Custom Resource Object (CRO). + +2. `KubeDB` Provisioner operator watches the `FerretDB` CRO. + +3. When the operator finds a `FerretDB` CR, it creates `PetSet` and related necessary stuff like secrets, services, etc. + +4. Then, in order to reconfigure the TLS configuration of the `FerretDB` the user creates a `FerretDBOpsRequest` CR with desired information. + +5. `KubeDB` Ops-manager operator watches the `FerretDBOpsRequest` CR. + +6. When it finds a `FerretDBOpsRequest` CR, it pauses the `FerretDB` object which is referred from the `FerretDBOpsRequest`. So, the `KubeDB` Provisioner operator doesn't perform any operations on the `FerretDB` object during the reconfiguring TLS process. + +7. Then the `KubeDB` Ops-manager operator will add, remove, update or rotate TLS configuration based on the Ops Request yaml. + +8. Then the `KubeDB` Ops-manager operator will restart all the Pods of the ferretdb so that they restart with the new TLS configuration defined in the `FerretDBOpsRequest` CR. + +9. After the successful reconfiguring of the `FerretDB` TLS, the `KubeDB` Ops-manager operator resumes the `FerretDB` 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 TLS configuration of a FerretDB using `FerretDBOpsRequest` CRD. \ No newline at end of file diff --git a/docs/guides/ferretdb/reconfigure-tls/reconfigure-tls.md b/docs/guides/ferretdb/reconfigure-tls/reconfigure-tls.md new file mode 100644 index 0000000000..2217344798 --- /dev/null +++ b/docs/guides/ferretdb/reconfigure-tls/reconfigure-tls.md @@ -0,0 +1,1072 @@ +--- +title: Reconfigure FerretDB TLS/SSL Encryption +menu: + docs_{{ .version }}: + identifier: fr-reconfigure-tls-rs + name: Reconfigure FerretDB TLS/SSL Encryption + parent: fr-reconfigure-tls + weight: 10 +menu_name: docs_{{ .version }} +section_menu_id: guides +--- + +> New to KubeDB? Please start [here](/docs/README.md). + +# Reconfigure FerretDB TLS/SSL (Transport Encryption) + +KubeDB supports reconfigure i.e. add, remove, update and rotation of TLS/SSL certificates for existing FerretDB database via a FerretDBOpsRequest. This tutorial will show you how to use KubeDB to reconfigure TLS/SSL encryption. + +## 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 [`cert-manger`](https://cert-manager.io/docs/installation/) v1.0.0 or later to your cluster to manage your SSL/TLS certificates. + +- 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/ferretdb](https://github.com/kubedb/docs/tree/{{< param "info.version" >}}/docs/examples/ferretdb) folder in GitHub repository [kubedb/docs](https://github.com/kubedb/docs). + +## Add TLS to a FerretDB + +Here, We are going to create a FerretDB database without TLS and then reconfigure the ferretdb to use TLS. + +### Deploy FerretDB without TLS + +In this section, we are going to deploy a FerretDB without TLS. In the next few sections we will reconfigure TLS using `FerretDBOpsRequest` CRD. Below is the YAML of the `FerretDB` CR that we are going to create, + +```yaml +apiVersion: kubedb.com/v1alpha2 +kind: FerretDB +metadata: + name: ferretdb-x + namespace: demo +spec: + version: "1.18.0" + storage: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 500Mi + backend: + externallyManaged: false + replicas: 2 +``` + +Let's create the `FerretDB` CR we have shown above, + +```bash +$ kubectl create -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/ferretdb/reconfigure-tls/ferretdb.yaml +ferretdb.kubedb.com/ferretdb created +``` + +Now, wait until `ferretdb` has status `Ready`. i.e, + +```bash +$ kubectl get fr -n demo +NAME NAMESPACE VERSION STATUS AGE +ferretdb demo 1.18.0 Ready 75s + +$ kubectl dba describe ferretdb ferretdb -n demo +Name: ferretdb +Namespace: demo +Labels: +Annotations: +API Version: kubedb.com/v1alpha2 +Kind: FerretDB +Metadata: + Creation Timestamp: 2024-10-17T11:04:08Z + Finalizers: + kubedb.com + Generation: 4 + Resource Version: 158199 + UID: 7da85335-bac0-4247-ad69-85a7c44831df +Spec: + Auth Secret: + Name: ferretdb-auth + Backend: + Externally Managed: false + Linked DB: ferretdb + Postgres Ref: + Name: ferretdb-pg-backend + Namespace: demo + Version: 13.13 + Deletion Policy: WipeOut + Health Checker: + Failure Threshold: 1 + Period Seconds: 10 + Timeout Seconds: 10 + Pod Template: + Controller: + Metadata: + Spec: + Containers: + Name: ferretdb + Resources: + Limits: + Memory: 1Gi + Requests: + Cpu: 500m + Memory: 1Gi + Security Context: + Allow Privilege Escalation: false + Capabilities: + Drop: + ALL + Run As Group: 1000 + Run As Non Root: true + Run As User: 1000 + Seccomp Profile: + Type: RuntimeDefault + Pod Placement Policy: + Name: default + Security Context: + Fs Group: 1000 + Replicas: 2 + Ssl Mode: disabled + Storage: + Access Modes: + ReadWriteOnce + Resources: + Requests: + Storage: 500Mi + Storage Type: Durable + Version: 1.18.0 +Status: + Conditions: + Last Transition Time: 2024-10-17T11:04:08Z + Message: The KubeDB operator has started the provisioning of FerretDB: demo/ferretdb + Observed Generation: 2 + Reason: DatabaseProvisioningStartedSuccessfully + Status: True + Type: ProvisioningStarted + Last Transition Time: 2024-10-17T11:05:04Z + Message: All replicas are ready for FerretDB demo/ferretdb + Observed Generation: 4 + Reason: AllReplicasReady + Status: True + Type: ReplicaReady + Last Transition Time: 2024-10-17T11:05:14Z + Message: The FerretDB: demo/ferretdb is accepting client requests. + Observed Generation: 4 + Reason: DatabaseAcceptingConnectionRequest + Status: True + Type: AcceptingConnection + Last Transition Time: 2024-10-17T11:05:14Z + Message: The FerretDB: demo/ferretdb is ready. + Observed Generation: 4 + Reason: ReadinessCheckSucceeded + Status: True + Type: Ready + Last Transition Time: 2024-10-17T11:05:14Z + Message: The FerretDB: demo/ferretdb is successfully provisioned. + Observed Generation: 4 + Reason: DatabaseSuccessfullyProvisioned + Status: True + Type: Provisioned + Phase: Ready +Events: +``` + +### Create Issuer/ ClusterIssuer + +Now, We are going to create an example `Issuer` that will be used to enable SSL/TLS in FerretDB. Alternatively, you can follow this [cert-manager tutorial](https://cert-manager.io/docs/configuration/ca/) to create your own `Issuer`. + +- Start off by generating a ca certificates using openssl. + +```bash +$ openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout ./ca.key -out ./ca.crt -subj "/CN=ca/O=kubedb" +Generating a RSA private key +................+++++ +........................+++++ +writing new private key to './ca.key' +----- +``` + +- Now we are going to create a ca-secret using the certificate files that we have just generated. + +```bash +$ kubectl create secret tls ferretdb-ca \ + --cert=ca.crt \ + --key=ca.key \ + --namespace=demo +secret/ferretdb-ca created +``` + +Now, Let's create an `Issuer` using the `ferretdb-ca` secret that we have just created. The `YAML` file looks like this: + +```yaml +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: ferretdb-ca-issuer + namespace: demo +spec: + ca: + secretName: ferretdb-ca +``` + +Let's apply the `YAML` file: + +```bash +$ kubectl create -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/ferretdb/reconfigure-tls/issuer.yaml +issuer.cert-manager.io/ferretdb-issuer created +``` + +### Create FerretDBOpsRequest + +In order to add TLS to the ferretdb, we have to create a `FerretDBOpsRequest` CRO with our created issuer. Below is the YAML of the `FerretDBOpsRequest` CRO that we are going to create, + +```yaml +apiVersion: ops.kubedb.com/v1alpha1 +kind: FerretDBOpsRequest +metadata: + name: frops-add-tls + namespace: demo +spec: + type: ReconfigureTLS + databaseRef: + name: ferretdb + tls: + issuerRef: + name: ferretdb-ca-issuer + kind: Issuer + apiGroup: "cert-manager.io" + timeout: 5m + apply: IfReady +``` + +Here, + +- `spec.databaseRef.name` specifies that we are performing reconfigure TLS operation on `mg-rs` database. +- `spec.type` specifies that we are performing `ReconfigureTLS` on our database. +- `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/ferretdb/concepts/ferretdb.md#spectls). +- `spec.tls.sslMode` is the ssl mode of the server. You can see the details [here](/docs/guides/ferretdb/concepts/ferretdb.md#specsslmode). +- The meaning of `spec.timeout` & `spec.apply` fields will be found [here](/docs/guides/ferretdb/concepts/opsrequest.md#spectimeout) + +Let's create the `FerretDBOpsRequest` CR we have shown above, + +```bash +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/ferretdb/reconfigure-tls/frops-add-tls.yaml +ferretdbopsrequest.ops.kubedb.com/frops-add-tls created +``` + +#### Verify TLS Enabled Successfully + +Let's wait for `FerretDBOpsRequest` to be `Successful`. Run the following command to watch `FerretDBOpsRequest` CRO, + +```bash +$ watch kubectl get ferretdbopsrequest -n demo +Every 2.0s: kubectl get ferretdbopsrequest -n demo +NAME TYPE STATUS AGE +frops-add-tls ReconfigureTLS Successful 13m +``` + +We can see from the above output that the `FerretDBOpsRequest` has succeeded. If we describe the `FerretDBOpsRequest` we will get an overview of the steps that were followed. + +```bash +$ kubectl describe ferretdbopsrequest -n demo frops-add-tls +Name: frops-add-tls +Namespace: demo +Labels: +Annotations: +API Version: ops.kubedb.com/v1alpha1 +Kind: FerretDBOpsRequest +Metadata: + Creation Timestamp: 2024-10-17T11:15:12Z + Generation: 1 + Resource Version: 159329 + UID: 071189ab-275f-4a25-99b9-72da3fa2fb6a +Spec: + Apply: IfReady + Database Ref: + Name: ferretdb + Timeout: 5m + Tls: + Issuer Ref: + API Group: cert-manager.io + Kind: Issuer + Name: ferretdb-ca-issuer + Type: ReconfigureTLS +Status: + Conditions: + Last Transition Time: 2024-10-17T11:15:12Z + Message: FerretDB ops-request has started to reconfigure tls for FerretDB nodes + Observed Generation: 1 + Reason: ReconfigureTLS + Status: True + Type: ReconfigureTLS + Last Transition Time: 2024-10-17T11:15:15Z + Message: Successfully paused database + Observed Generation: 1 + Reason: DatabasePauseSucceeded + Status: True + Type: DatabasePauseSucceeded + Last Transition Time: 2024-10-17T11:15:20Z + Message: get certificate; ConditionStatus:True + Observed Generation: 1 + Status: True + Type: GetCertificate + Last Transition Time: 2024-10-17T11:15:20Z + Message: ready condition; ConditionStatus:True + Observed Generation: 1 + Status: True + Type: ReadyCondition + Last Transition Time: 2024-10-17T11:15:20Z + Message: issuing condition; ConditionStatus:True + Observed Generation: 1 + Status: True + Type: IssuingCondition + Last Transition Time: 2024-10-17T11:15:20Z + Message: Successfully synced all certificates + Observed Generation: 1 + Reason: CertificateSynced + Status: True + Type: CertificateSynced + Last Transition Time: 2024-10-17T11:15:25Z + Message: successfully reconciled the FerretDB with tls configuration + Observed Generation: 1 + Reason: UpdatePetSets + Status: True + Type: UpdatePetSets + Last Transition Time: 2024-10-17T11:15:30Z + Message: get pod; ConditionStatus:True; PodName:ferretdb-0 + Observed Generation: 1 + Status: True + Type: GetPod--ferretdb-0 + Last Transition Time: 2024-10-17T11:15:31Z + Message: evict pod; ConditionStatus:True; PodName:ferretdb-0 + Observed Generation: 1 + Status: True + Type: EvictPod--ferretdb-0 + Last Transition Time: 2024-10-17T11:15:35Z + Message: check pod running; ConditionStatus:True; PodName:ferretdb-0 + Observed Generation: 1 + Status: True + Type: CheckPodRunning--ferretdb-0 + Last Transition Time: 2024-10-17T11:15:40Z + Message: get pod; ConditionStatus:True; PodName:ferretdb-1 + Observed Generation: 1 + Status: True + Type: GetPod--ferretdb-1 + Last Transition Time: 2024-10-17T11:15:41Z + Message: evict pod; ConditionStatus:True; PodName:ferretdb-1 + Observed Generation: 1 + Status: True + Type: EvictPod--ferretdb-1 + Last Transition Time: 2024-10-17T11:15:45Z + Message: check pod running; ConditionStatus:True; PodName:ferretdb-1 + Observed Generation: 1 + Status: True + Type: CheckPodRunning--ferretdb-1 + Last Transition Time: 2024-10-17T11:15:50Z + Message: Successfully restarted all nodes + Observed Generation: 1 + Reason: RestartNodes + Status: True + Type: RestartNodes + Last Transition Time: 2024-10-17T11:15:51Z + Message: Successfully completed the ReconfigureTLS for FerretDB + Observed Generation: 1 + Reason: Successful + Status: True + Type: Successful + Observed Generation: 1 + Phase: Successful +Events: + Type Reason Age From Message + ---- ------ ---- ---- ------- + Normal Starting 13m KubeDB Ops-manager Operator Start processing for FerretDBOpsRequest: demo/frops-add-tls + Normal Starting 13m KubeDB Ops-manager Operator Pausing FerretDB database: demo/ferretdb + Normal Successful 13m KubeDB Ops-manager Operator Successfully paused FerretDB database: demo/ferretdb for FerretDBOpsRequest: frops-add-tls + Warning get certificate; ConditionStatus:True 13m KubeDB Ops-manager Operator get certificate; ConditionStatus:True + Warning ready condition; ConditionStatus:True 13m KubeDB Ops-manager Operator ready condition; ConditionStatus:True + Warning issuing condition; ConditionStatus:True 13m KubeDB Ops-manager Operator issuing condition; ConditionStatus:True + Warning get certificate; ConditionStatus:True 13m KubeDB Ops-manager Operator get certificate; ConditionStatus:True + Warning ready condition; ConditionStatus:True 13m KubeDB Ops-manager Operator ready condition; ConditionStatus:True + Warning issuing condition; ConditionStatus:True 13m KubeDB Ops-manager Operator issuing condition; ConditionStatus:True + Normal CertificateSynced 13m KubeDB Ops-manager Operator Successfully synced all certificates + Normal UpdatePetSets 13m KubeDB Ops-manager Operator successfully reconciled the FerretDB with tls configuration + Warning get pod; ConditionStatus:True; PodName:ferretdb-0 13m KubeDB Ops-manager Operator get pod; ConditionStatus:True; PodName:ferretdb-0 + Warning evict pod; ConditionStatus:True; PodName:ferretdb-0 13m KubeDB Ops-manager Operator evict pod; ConditionStatus:True; PodName:ferretdb-0 + Warning check pod running; ConditionStatus:True; PodName:ferretdb-0 13m KubeDB Ops-manager Operator check pod running; ConditionStatus:True; PodName:ferretdb-0 + Warning get pod; ConditionStatus:True; PodName:ferretdb-1 13m KubeDB Ops-manager Operator get pod; ConditionStatus:True; PodName:ferretdb-1 + Warning evict pod; ConditionStatus:True; PodName:ferretdb-1 13m KubeDB Ops-manager Operator evict pod; ConditionStatus:True; PodName:ferretdb-1 + Warning check pod running; ConditionStatus:True; PodName:ferretdb-1 13m KubeDB Ops-manager Operator check pod running; ConditionStatus:True; PodName:ferretdb-1 + Normal RestartNodes 13m KubeDB Ops-manager Operator Successfully restarted all nodes + Normal Starting 13m KubeDB Ops-manager Operator Resuming FerretDB database: demo/ferretdb + Normal Successful 13m KubeDB Ops-manager Operator Successfully resumed FerretDB database: demo/ferretdb for FerretDBOpsRequest: frops-add-tls +``` + +Now let's connect with this ferretdb with certs. We need save the client cert and key to two different files and make a pem file. +Additionally, to verify server, we need to store ca.crt. + +```bash +$ kubectl get secrets -n demo ferretdb-client-cert -o jsonpath='{.data.tls\.crt}' | base64 -d > client.crt +$ kubectl get secrets -n demo ferretdb-client-cert -o jsonpath='{.data.tls\.key}' | base64 -d > client.key +$ kubectl get secrets -n demo ferretdb-client-cert -o jsonpath='{.data.ca\.crt}' | base64 -d > ca.crt +$ cat client.crt client.key > client.pem +``` + +Now, we can connect to our FerretDB with these files with mongosh client. + +```bash +$ kubectl get secrets -n demo ferretdb-auth -o jsonpath='{.data.\username}' | base64 -d +postgres +$ kubectl get secrets -n demo ferretdb-auth -o jsonpath='{.data.\\password}' | base64 -d +l*jGp8u*El8WRSDJ + +$ kubectl port-forward svc/ferretdb -n demo 27017 +Forwarding from 127.0.0.1:27017 -> 27018 +Forwarding from [::1]:27017 -> 27018 +Handling connection for 27017 +Handling connection for 27017 +``` + +Now in another terminal + +```bash +$ mongosh 'mongodb://postgres:l*jGp8u*El8WRSDJ@localhost:27017/ferretdb?authMechanism=PLAIN&tls=true&tlsCertificateKeyFile=./client.pem&tlsCaFile=./ca.crt' +Current Mongosh Log ID: 65efeea2a3347fff66d04c70 +Connecting to: mongodb://@localhost:27017/ferretdb?authMechanism=PLAIN&directConnection=true&serverSelectionTimeoutMS=2000&appName=mongosh+2.1.5 +Using MongoDB: 7.0.42 +Using Mongosh: 2.1.5 + +For mongosh info see: https://docs.mongodb.com/mongodb-shell/ + +------ + The server generated these startup warnings when booting + 2024-03-12T05:56:50.979Z: Powered by FerretDB v1.23.0 and PostgreSQL 13.13 on x86_64-pc-linux-musl, compiled by gcc. + 2024-03-12T05:56:50.979Z: Please star us on GitHub: https://github.com/FerretDB/FerretDB. + 2024-03-12T05:56:50.979Z: The telemetry state is undecided. + 2024-03-12T05:56:50.979Z: Read more about FerretDB telemetry and how to opt out at https://beacon.ferretdb.io. +------ + +ferretdb> +``` +So, here we have connected using the client certificate and the connection is tls secured. So, we can safely assume that tls enabling was successful. + +## Rotate Certificate + +Now we are going to rotate the certificate of this database. First we can store the current expiration date of the certificate by exec into `ferretdb-0` pod. Certs are located in `/etc/certs/server/` path. + +### Create FerretDBOpsRequest + +Now we are going to increase it using a FerretDBOpsRequest. Below is the yaml of the ops request that we are going to create, + +```yaml +apiVersion: ops.kubedb.com/v1alpha1 +kind: FerretDBOpsRequest +metadata: + name: frops-rotate + namespace: demo +spec: + type: ReconfigureTLS + databaseRef: + name: ferretdb + tls: + rotateCertificates: true +``` + +Here, + +- `spec.databaseRef.name` specifies that we are performing reconfigure TLS operation on `ferretdb`. +- `spec.type` specifies that we are performing `ReconfigureTLS` on our ferretdb. +- `spec.tls.rotateCertificates` specifies that we want to rotate the certificate of this ferretdb. + +Let's create the `FerretDBOpsRequest` CR we have shown above, + +```bash +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/ferretdb/reconfigure-tls/frops-rotate.yaml +ferretdbopsrequest.ops.kubedb.com/frops-rotate created +``` + +#### Verify Certificate Rotated Successfully + +Let's wait for `FerretDBOpsRequest` to be `Successful`. Run the following command to watch `FerretDBOpsRequest` CRO, + +```bash +$ watch kubectl get ferretdbopsrequest -n demo +Every 2.0s: kubectl get ferretdbopsrequest -n demo +NAME TYPE STATUS AGE +frops-rotate ReconfigureTLS Successful 113s +``` + +We can see from the above output that the `FerretDBOpsRequest` has succeeded. If we describe the `FerretDBOpsRequest` we will get an overview of the steps that were followed. + +```bash +$ kubectl describe ferretdbopsrequest -n demo frops-rotate +Name: frops-rotate +Namespace: demo +Labels: +Annotations: +API Version: ops.kubedb.com/v1alpha1 +Kind: FerretDBOpsRequest +Metadata: + Creation Timestamp: 2024-10-17T11:37:29Z + Generation: 1 + Resource Version: 161772 + UID: 6d9acf23-2701-40f9-9187-da221f3e4158 +Spec: + Apply: IfReady + Database Ref: + Name: ferretdb + Tls: + Rotate Certificates: true + Type: ReconfigureTLS +Status: + Conditions: + Last Transition Time: 2024-10-17T11:37:29Z + Message: FerretDB ops-request has started to reconfigure tls for FerretDB nodes + Observed Generation: 1 + Reason: ReconfigureTLS + Status: True + Type: ReconfigureTLS + Last Transition Time: 2024-10-17T11:37:32Z + Message: Successfully paused database + Observed Generation: 1 + Reason: DatabasePauseSucceeded + Status: True + Type: DatabasePauseSucceeded + Last Transition Time: 2024-10-17T11:37:38Z + Message: get certificate; ConditionStatus:True + Observed Generation: 1 + Status: True + Type: GetCertificate + Last Transition Time: 2024-10-17T11:37:38Z + Message: ready condition; ConditionStatus:True + Observed Generation: 1 + Status: True + Type: ReadyCondition + Last Transition Time: 2024-10-17T11:37:38Z + Message: issuing condition; ConditionStatus:True + Observed Generation: 1 + Status: True + Type: IssuingCondition + Last Transition Time: 2024-10-17T11:37:38Z + Message: Successfully synced all certificates + Observed Generation: 1 + Reason: CertificateSynced + Status: True + Type: CertificateSynced + Last Transition Time: 2024-10-17T11:37:43Z + Message: successfully reconciled the FerretDB with tls configuration + Observed Generation: 1 + Reason: UpdatePetSets + Status: True + Type: UpdatePetSets + Last Transition Time: 2024-10-17T11:37:48Z + Message: get pod; ConditionStatus:True; PodName:ferretdb-0 + Observed Generation: 1 + Status: True + Type: GetPod--ferretdb-0 + Last Transition Time: 2024-10-17T11:37:48Z + Message: evict pod; ConditionStatus:True; PodName:ferretdb-0 + Observed Generation: 1 + Status: True + Type: EvictPod--ferretdb-0 + Last Transition Time: 2024-10-17T11:37:53Z + Message: check pod running; ConditionStatus:True; PodName:ferretdb-0 + Observed Generation: 1 + Status: True + Type: CheckPodRunning--ferretdb-0 + Last Transition Time: 2024-10-17T11:37:58Z + Message: get pod; ConditionStatus:True; PodName:ferretdb-1 + Observed Generation: 1 + Status: True + Type: GetPod--ferretdb-1 + Last Transition Time: 2024-10-17T11:37:58Z + Message: evict pod; ConditionStatus:True; PodName:ferretdb-1 + Observed Generation: 1 + Status: True + Type: EvictPod--ferretdb-1 + Last Transition Time: 2024-10-17T11:38:03Z + Message: check pod running; ConditionStatus:True; PodName:ferretdb-1 + Observed Generation: 1 + Status: True + Type: CheckPodRunning--ferretdb-1 + Last Transition Time: 2024-10-17T11:38:08Z + Message: Successfully restarted all nodes + Observed Generation: 1 + Reason: RestartNodes + Status: True + Type: RestartNodes + Last Transition Time: 2024-10-17T11:38:08Z + Message: Successfully completed the ReconfigureTLS for FerretDB + Observed Generation: 1 + Reason: Successful + Status: True + Type: Successful + Observed Generation: 1 + Phase: Successful +Events: + Type Reason Age From Message + ---- ------ ---- ---- ------- + Normal Starting 55s KubeDB Ops-manager Operator Start processing for FerretDBOpsRequest: demo/frops-rotate + Normal Starting 55s KubeDB Ops-manager Operator Pausing FerretDB database: demo/ferretdb + Normal Successful 55s KubeDB Ops-manager Operator Successfully paused FerretDB database: demo/ferretdb for FerretDBOpsRequest: frops-rotate + Warning get certificate; ConditionStatus:True 46s KubeDB Ops-manager Operator get certificate; ConditionStatus:True + Warning ready condition; ConditionStatus:True 46s KubeDB Ops-manager Operator ready condition; ConditionStatus:True + Warning issuing condition; ConditionStatus:True 46s KubeDB Ops-manager Operator issuing condition; ConditionStatus:True + Warning get certificate; ConditionStatus:True 46s KubeDB Ops-manager Operator get certificate; ConditionStatus:True + Warning ready condition; ConditionStatus:True 46s KubeDB Ops-manager Operator ready condition; ConditionStatus:True + Warning issuing condition; ConditionStatus:True 46s KubeDB Ops-manager Operator issuing condition; ConditionStatus:True + Normal CertificateSynced 46s KubeDB Ops-manager Operator Successfully synced all certificates + Normal UpdatePetSets 41s KubeDB Ops-manager Operator successfully reconciled the FerretDB with tls configuration + Warning get pod; ConditionStatus:True; PodName:ferretdb-0 36s KubeDB Ops-manager Operator get pod; ConditionStatus:True; PodName:ferretdb-0 + Warning evict pod; ConditionStatus:True; PodName:ferretdb-0 36s KubeDB Ops-manager Operator evict pod; ConditionStatus:True; PodName:ferretdb-0 + Warning check pod running; ConditionStatus:True; PodName:ferretdb-0 31s KubeDB Ops-manager Operator check pod running; ConditionStatus:True; PodName:ferretdb-0 + Warning get pod; ConditionStatus:True; PodName:ferretdb-1 26s KubeDB Ops-manager Operator get pod; ConditionStatus:True; PodName:ferretdb-1 + Warning evict pod; ConditionStatus:True; PodName:ferretdb-1 26s KubeDB Ops-manager Operator evict pod; ConditionStatus:True; PodName:ferretdb-1 + Warning check pod running; ConditionStatus:True; PodName:ferretdb-1 21s KubeDB Ops-manager Operator check pod running; ConditionStatus:True; PodName:ferretdb-1 + Normal RestartNodes 16s KubeDB Ops-manager Operator Successfully restarted all nodes + Normal Starting 16s KubeDB Ops-manager Operator Resuming FerretDB database: demo/ferretdb + Normal Successful 16s KubeDB Ops-manager Operator Successfully resumed FerretDB database: demo/ferretdb for FerretDBOpsRequest: frops-rotate +``` + +As we can see from the above output, the certificate has been rotated successfully. + +## Change Issuer/ClusterIssuer + +Now, we are going to change the issuer of this database. + +- Let's create a new ca certificate and key using a different subject `CN=ca-update,O=kubedb-updated`. + +```bash +$ openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout ./ca.key -out ./ca.crt -subj "/CN=ca-updated/O=kubedb-updated" +Generating a RSA private key +..............................................................+++++ +......................................................................................+++++ +writing new private key to './ca.key' +----- +``` + +- Now we are going to create a new ca-secret using the certificate files that we have just generated. + +```bash +$ kubectl create secret tls ferretdb-new-ca \ + --cert=ca.crt \ + --key=ca.key \ + --namespace=demo +secret/ferretdb-new-ca created +``` + +Now, Let's create a new `Issuer` using the `ferretdb-new-ca` secret that we have just created. The `YAML` file looks like this: + +```yaml +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: fr-new-issuer + namespace: demo +spec: + ca: + secretName: ferretdb-new-ca +``` + +Let's apply the `YAML` file: + +```bash +$ kubectl create -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/ferretdb/reconfigure-tls/new-issuer.yaml +issuer.cert-manager.io/fr-new-issuer created +``` + +### Create FerretDBOpsRequest + +In order to use the new issuer to issue new certificates, we have to create a `FerretDBOpsRequest` CRO with the newly created issuer. Below is the YAML of the `FerretDBOpsRequest` CRO that we are going to create, + +```yaml +apiVersion: ops.kubedb.com/v1alpha1 +kind: FerretDBOpsRequest +metadata: + name: frops-change-issuer + namespace: demo +spec: + type: ReconfigureTLS + databaseRef: + name: ferretdb + tls: + issuerRef: + name: fr-new-issuer + kind: Issuer + apiGroup: "cert-manager.io" +``` + +Here, + +- `spec.databaseRef.name` specifies that we are performing reconfigure TLS operation on `ferretdb`. +- `spec.type` specifies that we are performing `ReconfigureTLS` on our ferretdb. +- `spec.tls.issuerRef` specifies the issuer name, kind and api group. + +Let's create the `FerretDBOpsRequest` CR we have shown above, + +```bash +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/ferretdb/reconfigure-tls/frops-change-issuer.yaml +ferretdbopsrequest.ops.kubedb.com/frops-change-issuer created +``` + +#### Verify Issuer is changed successfully + +Let's wait for `FerretDBOpsRequest` to be `Successful`. Run the following command to watch `FerretDBOpsRequest` CRO, + +```bash +$ watch kubectl get ferretdbopsrequest -n demo +Every 2.0s: kubectl get ferretdbopsrequest -n demo +NAME TYPE STATUS AGE +frops-change-issuer ReconfigureTLS Successful 87s +``` + +We can see from the above output that the `FerretDBOpsRequest` has succeeded. If we describe the `FerretDBOpsRequest` we will get an overview of the steps that were followed. + +```bash +$ kubectl describe ferretdbopsrequest -n demo frops-change-issuer +Name: frops-change-issuer +Namespace: demo +Labels: +Annotations: +API Version: ops.kubedb.com/v1alpha1 +Kind: FerretDBOpsRequest +Metadata: + Creation Timestamp: 2024-10-18T10:14:38Z + Generation: 1 + Resource Version: 423126 + UID: 1bf730e8-603e-4f30-b9ab-5a4e75d3a4d4 +Spec: + Apply: IfReady + Database Ref: + Name: ferretdb + Tls: + Issuer Ref: + API Group: cert-manager.io + Kind: Issuer + Name: fr-new-issuer + Type: ReconfigureTLS +Status: + Conditions: + Last Transition Time: 2024-10-18T10:14:38Z + Message: FerretDB ops-request has started to reconfigure tls for FerretDB nodes + Observed Generation: 1 + Reason: ReconfigureTLS + Status: True + Type: ReconfigureTLS + Last Transition Time: 2024-10-18T10:14:41Z + Message: Successfully paused database + Observed Generation: 1 + Reason: DatabasePauseSucceeded + Status: True + Type: DatabasePauseSucceeded + Last Transition Time: 2024-10-18T10:14:46Z + Message: get certificate; ConditionStatus:True + Observed Generation: 1 + Status: True + Type: GetCertificate + Last Transition Time: 2024-10-18T10:14:46Z + Message: ready condition; ConditionStatus:True + Observed Generation: 1 + Status: True + Type: ReadyCondition + Last Transition Time: 2024-10-18T10:14:46Z + Message: issuing condition; ConditionStatus:True + Observed Generation: 1 + Status: True + Type: IssuingCondition + Last Transition Time: 2024-10-18T10:14:46Z + Message: Successfully synced all certificates + Observed Generation: 1 + Reason: CertificateSynced + Status: True + Type: CertificateSynced + Last Transition Time: 2024-10-18T10:14:51Z + Message: successfully reconciled the FerretDB with tls configuration + Observed Generation: 1 + Reason: UpdatePetSets + Status: True + Type: UpdatePetSets + Last Transition Time: 2024-10-18T10:14:56Z + Message: get pod; ConditionStatus:True; PodName:ferretdb-0 + Observed Generation: 1 + Status: True + Type: GetPod--ferretdb-0 + Last Transition Time: 2024-10-18T10:14:56Z + Message: evict pod; ConditionStatus:True; PodName:ferretdb-0 + Observed Generation: 1 + Status: True + Type: EvictPod--ferretdb-0 + Last Transition Time: 2024-10-18T10:15:01Z + Message: check pod running; ConditionStatus:True; PodName:ferretdb-0 + Observed Generation: 1 + Status: True + Type: CheckPodRunning--ferretdb-0 + Last Transition Time: 2024-10-18T10:15:06Z + Message: get pod; ConditionStatus:True; PodName:ferretdb-1 + Observed Generation: 1 + Status: True + Type: GetPod--ferretdb-1 + Last Transition Time: 2024-10-18T10:15:06Z + Message: evict pod; ConditionStatus:True; PodName:ferretdb-1 + Observed Generation: 1 + Status: True + Type: EvictPod--ferretdb-1 + Last Transition Time: 2024-10-18T10:15:11Z + Message: check pod running; ConditionStatus:True; PodName:ferretdb-1 + Observed Generation: 1 + Status: True + Type: CheckPodRunning--ferretdb-1 + Last Transition Time: 2024-10-18T10:15:16Z + Message: Successfully restarted all nodes + Observed Generation: 1 + Reason: RestartNodes + Status: True + Type: RestartNodes + Last Transition Time: 2024-10-18T10:15:16Z + Message: Successfully completed the ReconfigureTLS for FerretDB + Observed Generation: 1 + Reason: Successful + Status: True + Type: Successful + Observed Generation: 1 + Phase: Successful +Events: + Type Reason Age From Message + ---- ------ ---- ---- ------- + Normal Starting 88s KubeDB Ops-manager Operator Start processing for FerretDBOpsRequest: demo/frops-change-issuer + Normal Starting 88s KubeDB Ops-manager Operator Pausing FerretDB database: demo/ferretdb + Normal Successful 88s KubeDB Ops-manager Operator Successfully paused FerretDB database: demo/ferretdb for FerretDBOpsRequest: frops-change-issuer + Warning get certificate; ConditionStatus:True 80s KubeDB Ops-manager Operator get certificate; ConditionStatus:True + Warning ready condition; ConditionStatus:True 80s KubeDB Ops-manager Operator ready condition; ConditionStatus:True + Warning issuing condition; ConditionStatus:True 80s KubeDB Ops-manager Operator issuing condition; ConditionStatus:True + Warning get certificate; ConditionStatus:True 80s KubeDB Ops-manager Operator get certificate; ConditionStatus:True + Warning ready condition; ConditionStatus:True 80s KubeDB Ops-manager Operator ready condition; ConditionStatus:True + Warning issuing condition; ConditionStatus:True 80s KubeDB Ops-manager Operator issuing condition; ConditionStatus:True + Normal CertificateSynced 80s KubeDB Ops-manager Operator Successfully synced all certificates + Normal UpdatePetSets 75s KubeDB Ops-manager Operator successfully reconciled the FerretDB with tls configuration + Warning get pod; ConditionStatus:True; PodName:ferretdb-0 70s KubeDB Ops-manager Operator get pod; ConditionStatus:True; PodName:ferretdb-0 + Warning evict pod; ConditionStatus:True; PodName:ferretdb-0 70s KubeDB Ops-manager Operator evict pod; ConditionStatus:True; PodName:ferretdb-0 + Warning check pod running; ConditionStatus:True; PodName:ferretdb-0 65s KubeDB Ops-manager Operator check pod running; ConditionStatus:True; PodName:ferretdb-0 + Warning get pod; ConditionStatus:True; PodName:ferretdb-1 60s KubeDB Ops-manager Operator get pod; ConditionStatus:True; PodName:ferretdb-1 + Warning evict pod; ConditionStatus:True; PodName:ferretdb-1 60s KubeDB Ops-manager Operator evict pod; ConditionStatus:True; PodName:ferretdb-1 + Warning check pod running; ConditionStatus:True; PodName:ferretdb-1 55s KubeDB Ops-manager Operator check pod running; ConditionStatus:True; PodName:ferretdb-1 + Normal RestartNodes 50s KubeDB Ops-manager Operator Successfully restarted all nodes + Normal Starting 50s KubeDB Ops-manager Operator Resuming FerretDB database: demo/ferretdb + Normal Successful 50s KubeDB Ops-manager Operator Successfully resumed FerretDB database: demo/ferretdb for FerretDBOpsRequest: frops-change-issuer +``` + +Now, If exec ferretdb and find out the ca subject in `/etc/certs/server` location, we can see that the CN and O is updated according to out new ca.crt. + +We can see that subject name of this ca.crt matches the subject name of the new ca certificate that we have created. So, the issuer is changed successfully. + +## Remove TLS from the ferretdb + +Now, we are going to remove TLS from this ferretdb using a FerretDBOpsRequest. + +### Create FerretDBOpsRequest + +Below is the YAML of the `FerretDBOpsRequest` CRO that we are going to create, + +```yaml +apiVersion: ops.kubedb.com/v1alpha1 +kind: FerretDBOpsRequest +metadata: + name: frops-remove + namespace: demo +spec: + type: ReconfigureTLS + databaseRef: + name: ferretdb + tls: + remove: true +``` + +Here, + +- `spec.databaseRef.name` specifies that we are performing reconfigure TLS operation on `ferretdb`. +- `spec.type` specifies that we are performing `ReconfigureTLS` on our ferretdb. +- `spec.tls.remove` specifies that we want to remove tls from this ferretdb. + +Let's create the `FerretDBOpsRequest` CR we have shown above, + +```bash +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/ferretdb/reconfigure-tls/frops-remove.yaml +ferretdbopsrequest.ops.kubedb.com/frops-remove created +``` + +#### Verify TLS Removed Successfully + +Let's wait for `FerretDBOpsRequest` to be `Successful`. Run the following command to watch `FerretDBOpsRequest` CRO, + +```bash +$ wacth kubectl get ferretdbopsrequest -n demo +Every 2.0s: kubectl get ferretdbopsrequest -n demo +NAME TYPE STATUS AGE +frops-remove ReconfigureTLS Successful 65s +``` + +We can see from the above output that the `FerretDBOpsRequest` has succeeded. If we describe the `FerretDBOpsRequest` we will get an overview of the steps that were followed. + +```bash +$ kubectl describe ferretdbopsrequest -n demo frops-remove +Name: frops-remove +Namespace: demo +Labels: +Annotations: +API Version: ops.kubedb.com/v1alpha1 +Kind: FerretDBOpsRequest +Metadata: + Creation Timestamp: 2024-10-18T11:11:55Z + Generation: 1 + Resource Version: 428244 + UID: 28a6ba72-0a2d-47f1-97b0-1e9609845acc +Spec: + Apply: IfReady + Database Ref: + Name: ferretdb + Tls: + Remove: true + Type: ReconfigureTLS +Status: + Conditions: + Last Transition Time: 2024-10-18T11:11:55Z + Message: FerretDB ops-request has started to reconfigure tls for FerretDB nodes + Observed Generation: 1 + Reason: ReconfigureTLS + Status: True + Type: ReconfigureTLS + Last Transition Time: 2024-10-18T11:11:58Z + Message: Successfully paused database + Observed Generation: 1 + Reason: DatabasePauseSucceeded + Status: True + Type: DatabasePauseSucceeded + Last Transition Time: 2024-10-18T11:12:04Z + Message: successfully reconciled the FerretDB with tls configuration + Observed Generation: 1 + Reason: UpdatePetSets + Status: True + Type: UpdatePetSets + Last Transition Time: 2024-10-18T11:12:09Z + Message: get pod; ConditionStatus:True; PodName:ferretdb-0 + Observed Generation: 1 + Status: True + Type: GetPod--ferretdb-0 + Last Transition Time: 2024-10-18T11:12:09Z + Message: evict pod; ConditionStatus:True; PodName:ferretdb-0 + Observed Generation: 1 + Status: True + Type: EvictPod--ferretdb-0 + Last Transition Time: 2024-10-18T11:12:14Z + Message: check pod running; ConditionStatus:True; PodName:ferretdb-0 + Observed Generation: 1 + Status: True + Type: CheckPodRunning--ferretdb-0 + Last Transition Time: 2024-10-18T11:12:19Z + Message: get pod; ConditionStatus:True; PodName:ferretdb-1 + Observed Generation: 1 + Status: True + Type: GetPod--ferretdb-1 + Last Transition Time: 2024-10-18T11:12:19Z + Message: evict pod; ConditionStatus:True; PodName:ferretdb-1 + Observed Generation: 1 + Status: True + Type: EvictPod--ferretdb-1 + Last Transition Time: 2024-10-18T11:12:24Z + Message: check pod running; ConditionStatus:True; PodName:ferretdb-1 + Observed Generation: 1 + Status: True + Type: CheckPodRunning--ferretdb-1 + Last Transition Time: 2024-10-18T11:12:29Z + Message: Successfully restarted all nodes + Observed Generation: 1 + Reason: RestartNodes + Status: True + Type: RestartNodes + Last Transition Time: 2024-10-18T11:12:29Z + Message: Successfully completed the ReconfigureTLS for FerretDB + Observed Generation: 1 + Reason: Successful + Status: True + Type: Successful + Observed Generation: 1 + Phase: Successful +Events: + Type Reason Age From Message + ---- ------ ---- ---- ------- + Normal Starting 87s KubeDB Ops-manager Operator Start processing for FerretDBOpsRequest: demo/frops-remove + Normal Starting 87s KubeDB Ops-manager Operator Pausing FerretDB database: demo/ferretdb + Normal Successful 87s KubeDB Ops-manager Operator Successfully paused FerretDB database: demo/ferretdb for FerretDBOpsRequest: frops-remove + Normal UpdatePetSets 78s KubeDB Ops-manager Operator successfully reconciled the FerretDB with tls configuration + Warning get pod; ConditionStatus:True; PodName:ferretdb-0 73s KubeDB Ops-manager Operator get pod; ConditionStatus:True; PodName:ferretdb-0 + Warning evict pod; ConditionStatus:True; PodName:ferretdb-0 73s KubeDB Ops-manager Operator evict pod; ConditionStatus:True; PodName:ferretdb-0 + Warning check pod running; ConditionStatus:True; PodName:ferretdb-0 68s KubeDB Ops-manager Operator check pod running; ConditionStatus:True; PodName:ferretdb-0 + Warning get pod; ConditionStatus:True; PodName:ferretdb-1 63s KubeDB Ops-manager Operator get pod; ConditionStatus:True; PodName:ferretdb-1 + Warning evict pod; ConditionStatus:True; PodName:ferretdb-1 63s KubeDB Ops-manager Operator evict pod; ConditionStatus:True; PodName:ferretdb-1 + Warning check pod running; ConditionStatus:True; PodName:ferretdb-1 58s KubeDB Ops-manager Operator check pod running; ConditionStatus:True; PodName:ferretdb-1 + Normal RestartNodes 53s KubeDB Ops-manager Operator Successfully restarted all nodes + Normal Starting 53s KubeDB Ops-manager Operator Resuming FerretDB database: demo/ferretdb + Normal Successful 53s KubeDB Ops-manager Operator Successfully resumed FerretDB database: demo/ferretdb for FerretDBOpsRequest: frops-remove +``` + +Now, Let's try to connect with ferretdb without TLS certs. + +```bash +$ kubectl get secrets -n demo ferret-auth -o jsonpath='{.data.\username}' | base64 -d +postgres +$ kubectl get secrets -n demo ferret-auth -o jsonpath='{.data.\\password}' | base64 -d +l*jGp8u*El8WRSDJ + +$ kubectl port-forward svc/ferret -n demo 27017 +Forwarding from 127.0.0.1:27017 -> 27017 +Forwarding from [::1]:27017 -> 27017 +Handling connection for 27017 +Handling connection for 27017 +``` + +Now in another terminal + +```bash +$ mongosh 'mongodb://postgres:l*jGp8u*El8WRSDJ@localhost:27017/ferretdb?authMechanism=PLAIN' +Current Mongosh Log ID: 65efeea2a3347fff66d04c70 +Connecting to: mongodb://@localhost:27017/ferretdb?authMechanism=PLAIN&directConnection=true&serverSelectionTimeoutMS=2000&appName=mongosh+2.1.5 +Using MongoDB: 7.0.42 +Using Mongosh: 2.1.5 + +For mongosh info see: https://docs.mongodb.com/mongodb-shell/ + +------ + The server generated these startup warnings when booting + 2024-03-12T05:56:50.979Z: Powered by FerretDB v1.18.0 and PostgreSQL 13.13 on x86_64-pc-linux-musl, compiled by gcc. + 2024-03-12T05:56:50.979Z: Please star us on GitHub: https://github.com/FerretDB/FerretDB. + 2024-03-12T05:56:50.979Z: The telemetry state is undecided. + 2024-03-12T05:56:50.979Z: Read more about FerretDB telemetry and how to opt out at https://beacon.ferretdb.io. +------ + +ferretdb> +``` + +We can see that we can now connect without providing TLS certs. So TLS connection is successfully disabled + +## Cleaning up + +To clean up the Kubernetes resources created by this tutorial, run: + +```bash +kubectl delete ferretdb -n demo ferretdb +kubectl delete issuer -n demo ferretdb-issuer fr-new-issuer +kubectl delete ferretdbopsrequest -n demo frops-add-tls frops-remove frops-rotate frops-change-issuer +kubectl delete ns demo +``` + +## Next Steps + +- Detail concepts of [FerretDB object](/docs/guides/ferretdb/concepts/ferretdb.md). +- Monitor your FerretDB database with KubeDB using [out-of-the-box Prometheus operator](/docs/guides/ferretdb/monitoring/using-prometheus-operator.md). +- Monitor your FerretDB database with KubeDB using [out-of-the-box builtin-Prometheus](/docs/guides/ferretdb/monitoring/using-builtin-prometheus.md). +- Detail concepts of [FerretDB object](/docs/guides/ferretdb/concepts/ferretdb.md). +- Want to hack on KubeDB? Check our [contribution guidelines](/docs/CONTRIBUTING.md). diff --git a/docs/guides/ferretdb/restart/_index.md b/docs/guides/ferretdb/restart/_index.md new file mode 100644 index 0000000000..f63cdeaa1f --- /dev/null +++ b/docs/guides/ferretdb/restart/_index.md @@ -0,0 +1,10 @@ +--- +title: Restart FerretDB +menu: + docs_{{ .version }}: + identifier: fr-restart + name: Restart + parent: fr-ferretdb-guides + weight: 46 +menu_name: docs_{{ .version }} +--- diff --git a/docs/guides/ferretdb/restart/restart.md b/docs/guides/ferretdb/restart/restart.md new file mode 100644 index 0000000000..1441676233 --- /dev/null +++ b/docs/guides/ferretdb/restart/restart.md @@ -0,0 +1,174 @@ +--- +title: Restart FerretDB +menu: + docs_{{ .version }}: + identifier: fr-restart-details + name: Restart FerretDB + parent: fr-restart + weight: 10 +menu_name: docs_{{ .version }} +section_menu_id: guides +--- + +> New to KubeDB? Please start [here](/docs/README.md). + +# Restart FerretDB + +KubeDB supports restarting the FerretDB via a FerretDBOpsRequest. 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/ferretdb](https://github.com/kubedb/docs/tree/{{< param "info.version" >}}/docs/examples/ferretdb) folder in GitHub repository [kubedb/docs](https://github.com/kubedb/docs). + +## Deploy FerretDB + +In this section, we are going to deploy a FerretDB using KubeDB. + +```yaml +apiVersion: kubedb.com/v1alpha2 +kind: FerretDB +metadata: + name: ferretdb + namespace: demo +spec: + version: "1.23.0" + replicas: 1 + backend: + externallyManaged: false + storage: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 500Mi + deletionPolicy: WipeOut +``` + +Let's create the `FerretDB` CR we have shown above, + +```bash +$ kubectl create -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/ferretdb/restart/ferretdb.yaml +ferretdb.kubedb.com/ferretdb created +``` + +## Apply Restart opsRequest + +```yaml +apiVersion: ops.kubedb.com/v1alpha1 +kind: FerretDBOpsRequest +metadata: + name: restart-ferretdb + namespace: demo +spec: + type: Restart + databaseRef: + name: ferretdb + timeout: 3m + apply: Always +``` + +- `spec.type` specifies the Type of the ops Request +- `spec.databaseRef` holds the name of the FerretDB. The ferretdb should be available in the same namespace as the opsRequest +- The meaning of `spec.timeout` & `spec.apply` fields will be found [here](/docs/guides/ferretdb/concepts/opsrequest.md#spectimeout) + +Let's create the `FerretDBOpsRequest` CR we have shown above, + +```bash +$ kubectl create -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/ferretdb/restart/ops.yaml +ferretdbopsrequest.ops.kubedb.com/restart-ferretdb created +``` + +Now the Ops-manager operator will restart the pods one by one. + +```shell +$ kubectl get frops -n demo +NAME TYPE STATUS AGE +restart-ferretdb Restart Successful 2m15s + +$ kubectl get frops -n demo -oyaml restart-ferretdb +apiVersion: ops.kubedb.com/v1alpha1 +kind: FerretDBOpsRequest +metadata: + annotations: + kubectl.kubernetes.io/last-applied-configuration: | + {"apiVersion":"ops.kubedb.com/v1alpha1","kind":"FerretDBOpsRequest","metadata":{"annotations":{},"name":"restart-ferretdb","namespace":"demo"},"spec":{"apply":"Always","databaseRef":{"name":"ferretdb"},"timeout":"3m","type":"Restart"}} + creationTimestamp: "2024-10-21T12:38:38Z" + generation: 1 + name: restart-ferretdb + namespace: demo + resourceVersion: "367859" + uid: 0ca77cab-d354-43a4-ba85-c31f1f6e685d +spec: + apply: Always + databaseRef: + name: ferretdb + timeout: 3m + type: Restart +status: + conditions: + - lastTransitionTime: "2024-10-21T12:38:38Z" + message: FerretDBOpsRequest has started to restart FerretDB nodes + observedGeneration: 1 + reason: Restart + status: "True" + type: Restart + - lastTransitionTime: "2024-10-21T12:38:46Z" + message: get pod; ConditionStatus:True; PodName:ferretdb-0 + observedGeneration: 1 + status: "True" + type: GetPod--ferretdb-0 + - lastTransitionTime: "2024-10-21T12:38:46Z" + message: evict pod; ConditionStatus:True; PodName:ferretdb-0 + observedGeneration: 1 + status: "True" + type: EvictPod--ferretdb-0 + - lastTransitionTime: "2024-10-21T12:38:51Z" + message: check pod running; ConditionStatus:True; PodName:ferretdb-0 + observedGeneration: 1 + status: "True" + type: CheckPodRunning--ferretdb-0 + - lastTransitionTime: "2024-10-21T12:38:56Z" + message: Successfully restarted FerretDB nodes + observedGeneration: 1 + reason: RestartNodes + status: "True" + type: RestartNodes + - lastTransitionTime: "2024-10-21T12:38:56Z" + message: Controller has successfully restart the FerretDB 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 ferretdbopsrequest -n demo restart-ferretdb +kubectl delete ferretdb -n demo ferretdb +kubectl delete ns demo +``` + +## Next Steps + +- Detail concepts of [FerretDB object](/docs/guides/ferretdb/concepts/ferretdb.md). +- Monitor your FerretDB database with KubeDB using [out-of-the-box Prometheus operator](/docs/guides/ferretdb/monitoring/using-prometheus-operator.md). +- Monitor your FerretDB database with KubeDB using [out-of-the-box builtin-Prometheus](/docs/guides/ferretdb/monitoring/using-builtin-prometheus.md). +- Detail concepts of [FerretDB object](/docs/guides/ferretdb/concepts/ferretdb.md). +- Want to hack on KubeDB? Check our [contribution guidelines](/docs/CONTRIBUTING.md). diff --git a/docs/guides/ferretdb/scaling/_index.md b/docs/guides/ferretdb/scaling/_index.md new file mode 100644 index 0000000000..871b7e0178 --- /dev/null +++ b/docs/guides/ferretdb/scaling/_index.md @@ -0,0 +1,10 @@ +--- +title: Scaling FerretDB +menu: + docs_{{ .version }}: + identifier: fr-scaling + name: Scaling + parent: fr-ferretdb-guides + weight: 43 +menu_name: docs_{{ .version }} +--- \ No newline at end of file diff --git a/docs/guides/ferretdb/scaling/horizontal-scaling/_index.md b/docs/guides/ferretdb/scaling/horizontal-scaling/_index.md new file mode 100644 index 0000000000..123b627e9a --- /dev/null +++ b/docs/guides/ferretdb/scaling/horizontal-scaling/_index.md @@ -0,0 +1,10 @@ +--- +title: Horizontal Scaling +menu: + docs_{{ .version }}: + identifier: fr-horizontal-scaling + name: Horizontal Scaling + parent: fr-scaling + weight: 10 +menu_name: docs_{{ .version }} +--- \ No newline at end of file diff --git a/docs/guides/ferretdb/scaling/horizontal-scaling/horizontal-ops.md b/docs/guides/ferretdb/scaling/horizontal-scaling/horizontal-ops.md new file mode 100644 index 0000000000..4929ffbfac --- /dev/null +++ b/docs/guides/ferretdb/scaling/horizontal-scaling/horizontal-ops.md @@ -0,0 +1,432 @@ +--- +title: Horizontal Scaling FerretDB +menu: + docs_{{ .version }}: + identifier: fr-horizontal-scaling-ops + name: HorizontalScaling OpsRequest + parent: fr-horizontal-scaling + weight: 20 +menu_name: docs_{{ .version }} +section_menu_id: guides +--- + +> New to KubeDB? Please start [here](/docs/README.md). + +# Horizontal Scale FerretDB + +This guide will show you how to use `KubeDB` Ops-manager operator to scale the replicaset of a FerretDB. + +## 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: + - [FerretDB](/docs/guides/ferretdb/concepts/ferretdb.md) + - [FerretDBOpsRequest](/docs/guides/ferretdb/concepts/opsrequest.md) + - [Horizontal Scaling Overview](/docs/guides/ferretdb/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/ferretdb](/docs/examples/ferretdb) directory of [kubedb/docs](https://github.com/kubedb/docs) repository. + +## Apply Horizontal Scaling on ferretdb + +Here, we are going to deploy a `FerretDB` using a supported version by `KubeDB` operator. Then we are going to apply horizontal scaling on it. + +### Prepare FerretDB + +Now, we are going to deploy a `FerretDB` with version `1.23.0`. + +### Deploy FerretDB + +In this section, we are going to deploy a FerretDB. Then, in the next section we will scale the ferretdb using `FerretDBOpsRequest` CRD. Below is the YAML of the `FerretDB` CR that we are going to create, + +```yaml +apiVersion: kubedb.com/v1alpha2 +kind: FerretDB +metadata: + name: fr-horizontal + namespace: demo +spec: + version: "1.23.0" + replicas: 1 + backend: + externallyManaged: false + storage: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 500Mi + deletionPolicy: WipeOut +``` + +```bash +$ kubectl create -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/ferretdb/scaling/fr-horizontal.yaml +ferretdb.kubedb.com/fr-horizontal created +``` + +Now, wait until `fr-horizontal ` has status `Ready`. i.e, + +```bash +$ kubectl get fr -n demo +NAME TYPE VERSION STATUS AGE +fr-horizontal kubedb.com/v1alpha2 1.23.0 Ready 2m +``` + +Let's check the number of replicas this ferretdb has from the FerretDB object, number of pods the petset have, + +```bash +$ kubectl get ferretdb -n demo fr-horizontal -o json | jq '.spec.replicas' +1 + +$ kubectl get petset -n demo fr-horizontal -o json | jq '.spec.replicas' +1 +``` + +We can see from both command that the ferretdb has 1 replicas. + +We are now ready to apply the `FerretDBOpsRequest` CR to scale this ferretdb. + +## Scale Up Replicas + +Here, we are going to scale up the replicas of the ferretdb to meet the desired number of replicas after scaling. + +#### Create FerretDBOpsRequest + +In order to scale up the replicas of the ferretdb, we have to create a `FerretDBOpsRequest` CR with our desired replicas. Below is the YAML of the `FerretDBOpsRequest` CR that we are going to create, + +```yaml +apiVersion: ops.kubedb.com/v1alpha1 +kind: FerretDBOpsRequest +metadata: + name: ferretdb-horizontal-scale-up + namespace: demo +spec: + type: HorizontalScaling + databaseRef: + name: fr-horizontal + horizontalScaling: + node: 3 +``` + +Here, + +- `spec.databaseRef.name` specifies that we are performing horizontal scaling operation on `fr-horizontal` ferretdb. +- `spec.type` specifies that we are performing `HorizontalScaling` on our ferretdb. +- `spec.horizontalScaling.replicas` specifies the desired replicas after scaling. + +Let's create the `FerretDBOpsRequest` CR we have shown above, + +```bash +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/ferretdb/scaling/horizontal-scaling/frops-hscale-up-ops.yaml +ferretdbopsrequest.ops.kubedb.com/ferretdb-horizontal-scale-up created +``` + +#### Verify replicas scaled up successfully + +If everything goes well, `KubeDB` Ops-manager operator will update the replicas of `FerretDB` object and related `PetSet`. + +Let's wait for `FerretDBOpsRequest` to be `Successful`. Run the following command to watch `FerretDBOpsRequest` CR, + +```bash +$ watch kubectl get ferretdbopsrequest -n demo +Every 2.0s: kubectl get ferretdbopsrequest -n demo +NAME TYPE STATUS AGE +ferretdb-horizontal-scale-up HorizontalScaling Successful 102s +``` + +We can see from the above output that the `FerretDBOpsRequest` has succeeded. If we describe the `FerretDBOpsRequest` we will get an overview of the steps that were followed to scale the ferretdb. + +```bash +$ kubectl describe ferretdbopsrequest -n demo ferretdb-horizontal-scale-up +Name: ferretdb-horizontal-scale-up +Namespace: demo +Labels: +Annotations: +API Version: ops.kubedb.com/v1alpha1 +Kind: FerretDBOpsRequest +Metadata: + Creation Timestamp: 2024-10-21T10:03:39Z + Generation: 1 + Resource Version: 353610 + UID: ce6c9e66-6196-4746-851a-ea49084eda05 +Spec: + Apply: IfReady + Database Ref: + Name: fr-horizontal + Horizontal Scaling: + Node: 3 + Type: HorizontalScaling +Status: + Conditions: + Last Transition Time: 2024-10-21T10:04:30Z + Message: FerretDB ops-request has started to horizontally scaling the nodes + Observed Generation: 1 + Reason: HorizontalScaling + Status: True + Type: HorizontalScaling + Last Transition Time: 2024-10-21T10:04:33Z + Message: Successfully paused database + Observed Generation: 1 + Reason: DatabasePauseSucceeded + Status: True + Type: DatabasePauseSucceeded + Last Transition Time: 2024-10-21T10:04:58Z + Message: Successfully Scaled Up Node + Observed Generation: 1 + Reason: HorizontalScaleUp + Status: True + Type: HorizontalScaleUp + Last Transition Time: 2024-10-21T10:04:38Z + Message: patch petset; ConditionStatus:True; PodName:fr-horizontal-1 + Observed Generation: 1 + Status: True + Type: PatchPetset--fr-horizontal-1 + Last Transition Time: 2024-10-21T10:04:43Z + Message: is pod ready; ConditionStatus:True; PodName:fr-horizontal-1 + Observed Generation: 1 + Status: True + Type: IsPodReady--fr-horizontal-1 + Last Transition Time: 2024-10-21T10:04:43Z + Message: client failure; ConditionStatus:True; PodName:fr-horizontal-1 + Observed Generation: 1 + Status: True + Type: ClientFailure--fr-horizontal-1 + Last Transition Time: 2024-10-21T10:04:43Z + Message: is node healthy; ConditionStatus:True; PodName:fr-horizontal-1 + Observed Generation: 1 + Status: True + Type: IsNodeHealthy--fr-horizontal-1 + Last Transition Time: 2024-10-21T10:04:48Z + Message: patch petset; ConditionStatus:True; PodName:fr-horizontal-2 + Observed Generation: 1 + Status: True + Type: PatchPetset--fr-horizontal-2 + Last Transition Time: 2024-10-21T10:04:48Z + Message: fr-horizontal already has desired replicas + Observed Generation: 1 + Reason: HorizontalScale + Status: True + Type: HorizontalScale + Last Transition Time: 2024-10-21T10:04:53Z + Message: is pod ready; ConditionStatus:True; PodName:fr-horizontal-2 + Observed Generation: 1 + Status: True + Type: IsPodReady--fr-horizontal-2 + Last Transition Time: 2024-10-21T10:04:53Z + Message: client failure; ConditionStatus:True; PodName:fr-horizontal-2 + Observed Generation: 1 + Status: True + Type: ClientFailure--fr-horizontal-2 + Last Transition Time: 2024-10-21T10:04:53Z + Message: is node healthy; ConditionStatus:True; PodName:fr-horizontal-2 + Observed Generation: 1 + Status: True + Type: IsNodeHealthy--fr-horizontal-2 + Last Transition Time: 2024-10-21T10:04:58Z + Message: Successfully updated FerretDB + Observed Generation: 1 + Reason: UpdateDatabase + Status: True + Type: UpdateDatabase + Last Transition Time: 2024-10-21T10:04:58Z + Message: Successfully completed the HorizontalScaling for FerretDB + Observed Generation: 1 + Reason: Successful + Status: True + Type: Successful + Observed Generation: 1 + Phase: Successful +Events: + Type Reason Age From Message + ---- ------ ---- ---- ------- + Normal Starting 67s KubeDB Ops-manager Operator Start processing for FerretDBOpsRequest: demo/ferretdb-horizontal-scale-up + Normal Starting 67s KubeDB Ops-manager Operator Pausing FerretDB database: demo/fr-horizontal + Normal Successful 67s KubeDB Ops-manager Operator Successfully paused FerretDB database: demo/fr-horizontal for FerretDBOpsRequest: ferretdb-horizontal-scale-up + Warning patch petset; ConditionStatus:True; PodName:fr-horizontal-1 59s KubeDB Ops-manager Operator patch petset; ConditionStatus:True; PodName:fr-horizontal-1 + Warning is pod ready; ConditionStatus:True; PodName:fr-horizontal-1 54s KubeDB Ops-manager Operator is pod ready; ConditionStatus:True; PodName:fr-horizontal-1 + Warning client failure; ConditionStatus:True; PodName:fr-horizontal-1 54s KubeDB Ops-manager Operator client failure; ConditionStatus:True; PodName:fr-horizontal-1 + Warning is node healthy; ConditionStatus:True; PodName:fr-horizontal-1 54s KubeDB Ops-manager Operator is node healthy; ConditionStatus:True; PodName:fr-horizontal-1 + Warning patch petset; ConditionStatus:True; PodName:fr-horizontal-2 49s KubeDB Ops-manager Operator patch petset; ConditionStatus:True; PodName:fr-horizontal-2 + Warning is pod ready; ConditionStatus:True; PodName:fr-horizontal-2 44s KubeDB Ops-manager Operator is pod ready; ConditionStatus:True; PodName:fr-horizontal-2 + Warning client failure; ConditionStatus:True; PodName:fr-horizontal-2 44s KubeDB Ops-manager Operator client failure; ConditionStatus:True; PodName:fr-horizontal-2 + Warning is node healthy; ConditionStatus:True; PodName:fr-horizontal-2 44s KubeDB Ops-manager Operator is node healthy; ConditionStatus:True; PodName:fr-horizontal-2 + Normal HorizontalScaleUp 39s KubeDB Ops-manager Operator Successfully Scaled Up Node + Normal UpdateDatabase 39s KubeDB Ops-manager Operator Successfully updated FerretDB + Normal Starting 39s KubeDB Ops-manager Operator Resuming FerretDB database: demo/fr-horizontal + Normal Successful 39s KubeDB Ops-manager Operator Successfully resumed FerretDB database: demo/fr-horizontal for FerretDBOpsRequest: ferretdb-horizontal-scale-up +``` + +Now, we are going to verify the number of replicas this ferretdb has from the FerretDB object, number of pods the petset have, + +```bash +$ kubectl get fr -n demo fr-horizontal -o json | jq '.spec.replicas' +3 + +$ kubectl get petset -n demo fr-horizontal -o json | jq '.spec.replicas' +3 +``` +From all the above outputs we can see that the replicas of the ferretdb is `3`. That means we have successfully scaled up the replicas of the FerretDB. + + +### Scale Down Replicas + +Here, we are going to scale down the replicas of the ferretdb to meet the desired number of replicas after scaling. + +#### Create FerretDBOpsRequest + +In order to scale down the replicas of the ferretdb, we have to create a `FerretDBOpsRequest` CR with our desired replicas. Below is the YAML of the `FerretDBOpsRequest` CR that we are going to create, + +```yaml +apiVersion: ops.kubedb.com/v1alpha1 +kind: FerretDBOpsRequest +metadata: + name: ferretdb-horizontal-scale-down + namespace: demo +spec: + type: HorizontalScaling + databaseRef: + name: fr-horizontal + horizontalScaling: + node: 2 +``` + +Here, + +- `spec.databaseRef.name` specifies that we are performing horizontal scaling down operation on `fr-horizontal` ferretdb. +- `spec.type` specifies that we are performing `HorizontalScaling` on our ferretdb. +- `spec.horizontalScaling.replicas` specifies the desired replicas after scaling. + +Let's create the `FerretDBOpsRequest` CR we have shown above, + +```bash +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/ferretdb/scaling/horizontal-scaling/frops-hscale-down-ops.yaml +ferretdbopsrequest.ops.kubedb.com/ferretdb-horizontal-scale-down created +``` + +#### Verify replicas scaled down successfully + +If everything goes well, `KubeDB` Ops-manager operator will update the replicas of `FerretDB` object and related `PetSet`. + +Let's wait for `FerretDBOpsRequest` to be `Successful`. Run the following command to watch `FerretDBOpsRequest` CR, + +```bash +$ watch kubectl get ferretdbopsrequest -n demo +Every 2.0s: kubectl get ferretdbopsrequest -n demo +NAME TYPE STATUS AGE +ferretdb-horizontal-scale-down HorizontalScaling Successful 40s +``` + +We can see from the above output that the `FerretDBOpsRequest` has succeeded. If we describe the `FerretDBOpsRequest` we will get an overview of the steps that were followed to scale the ferretdb. + +```bash +$ kubectl describe ferretdbopsrequest -n demo ferretdb-horizontal-scale-down +Name: ferretdb-horizontal-scale-down +Namespace: demo +Labels: +Annotations: +API Version: ops.kubedb.com/v1alpha1 +Kind: FerretDBOpsRequest +Metadata: + Creation Timestamp: 2024-10-21T10:06:42Z + Generation: 1 + Resource Version: 353838 + UID: 69cb9e8a-ec89-41e2-9e91-ce61a68044b9 +Spec: + Apply: IfReady + Database Ref: + Name: fr-horizontal + Horizontal Scaling: + Node: 2 + Type: HorizontalScaling +Status: + Conditions: + Last Transition Time: 2024-10-21T10:06:42Z + Message: FerretDB ops-request has started to horizontally scaling the nodes + Observed Generation: 1 + Reason: HorizontalScaling + Status: True + Type: HorizontalScaling + Last Transition Time: 2024-10-21T10:06:45Z + Message: Successfully paused database + Observed Generation: 1 + Reason: DatabasePauseSucceeded + Status: True + Type: DatabasePauseSucceeded + Last Transition Time: 2024-10-21T10:07:00Z + Message: Successfully Scaled Down Node + Observed Generation: 1 + Reason: HorizontalScaleDown + Status: True + Type: HorizontalScaleDown + Last Transition Time: 2024-10-21T10:06:50Z + Message: patch petset; ConditionStatus:True; PodName:fr-horizontal-2 + Observed Generation: 1 + Status: True + Type: PatchPetset--fr-horizontal-2 + Last Transition Time: 2024-10-21T10:06:51Z + Message: fr-horizontal already has desired replicas + Observed Generation: 1 + Reason: HorizontalScale + Status: True + Type: HorizontalScale + Last Transition Time: 2024-10-21T10:06:55Z + Message: get pod; ConditionStatus:True; PodName:fr-horizontal-2 + Observed Generation: 1 + Status: True + Type: GetPod--fr-horizontal-2 + Last Transition Time: 2024-10-21T10:07:00Z + Message: Successfully updated FerretDB + Observed Generation: 1 + Reason: UpdateDatabase + Status: True + Type: UpdateDatabase + Last Transition Time: 2024-10-21T10:07:00Z + Message: Successfully completed the HorizontalScaling for FerretDB + Observed Generation: 1 + Reason: Successful + Status: True + Type: Successful + Observed Generation: 1 + Phase: Successful +Events: + Type Reason Age From Message + ---- ------ ---- ---- ------- + Normal Starting 55s KubeDB Ops-manager Operator Start processing for FerretDBOpsRequest: demo/ferretdb-horizontal-scale-down + Normal Starting 55s KubeDB Ops-manager Operator Pausing FerretDB database: demo/fr-horizontal + Normal Successful 55s KubeDB Ops-manager Operator Successfully paused FerretDB database: demo/fr-horizontal for FerretDBOpsRequest: ferretdb-horizontal-scale-down + Warning patch petset; ConditionStatus:True; PodName:fr-horizontal-2 47s KubeDB Ops-manager Operator patch petset; ConditionStatus:True; PodName:fr-horizontal-2 + Warning get pod; ConditionStatus:True; PodName:fr-horizontal-2 42s KubeDB Ops-manager Operator get pod; ConditionStatus:True; PodName:fr-horizontal-2 + Normal HorizontalScaleDown 37s KubeDB Ops-manager Operator Successfully Scaled Down Node + Normal UpdateDatabase 37s KubeDB Ops-manager Operator Successfully updated FerretDB + Normal Starting 37s KubeDB Ops-manager Operator Resuming FerretDB database: demo/fr-horizontal + Normal Successful 37s KubeDB Ops-manager Operator Successfully resumed FerretDB database: demo/fr-horizontal for FerretDBOpsRequest: ferretdb-horizontal-scale-down +``` + +Now, we are going to verify the number of replicas this ferretdb has from the FerretDB object, number of pods the petset have, + +```bash +$ kubectl get fr -n demo fr-horizontal -o json | jq '.spec.replicas' +2 + +$ kubectl get petset -n demo fr-horizontal -o json | jq '.spec.replicas' +2 +``` +From all the above outputs we can see that the replicas of the ferretdb is `2`. That means we have successfully scaled up the replicas of the FerretDB. + +## Cleaning Up + +To clean up the Kubernetes resources created by this tutorial, run: + +```bash +kubectl delete mg -n fr-horizontal +kubectl delete ferretdbopsrequest -n demo ferretdb-horizontal-scale-down +``` \ No newline at end of file diff --git a/docs/guides/ferretdb/scaling/horizontal-scaling/overview.md b/docs/guides/ferretdb/scaling/horizontal-scaling/overview.md new file mode 100644 index 0000000000..b177fdc2d4 --- /dev/null +++ b/docs/guides/ferretdb/scaling/horizontal-scaling/overview.md @@ -0,0 +1,54 @@ +--- +title: FerretDB Horizontal Scaling Overview +menu: + docs_{{ .version }}: + identifier: fr-horizontal-scaling-overview + name: Overview + parent: fr-horizontal-scaling + weight: 10 +menu_name: docs_{{ .version }} +section_menu_id: guides +--- + +> New to KubeDB? Please start [here](/docs/README.md). + +# FerretDB Horizontal Scaling + +This guide will give an overview on how KubeDB Ops-manager operator scales up or down `FerretDB` replicas of PetSet. + +## Before You Begin + +- You should be familiar with the following `KubeDB` concepts: + - [FerretDB](/docs/guides/ferretdb/concepts/ferretdb.md) + - [FerretDBOpsRequest](/docs/guides/ferretdb/concepts/opsrequest.md) + +## How Horizontal Scaling Process Works + +The following diagram shows how KubeDB Ops-manager operator scales up or down `FerretDB` database components. Open the image in a new tab to see the enlarged version. + +
+  Horizontal scaling process of FerretDB +
Fig: Horizontal scaling process of FerretDB
+
+ +The Horizontal scaling process consists of the following steps: + +1. At first, a user creates a `FerretDB` Custom Resource (CR). + +2. `KubeDB` Provisioner operator watches the `FerretDB` CR. + +3. When the operator finds a `FerretDB` CR, it creates `PetSet` and related necessary stuff like secrets, services, etc. + +4. Then, in order to scale the `PetSet` of the `FerretDB` database the user creates a `FerretDBOpsRequest` CR with desired information. + +5. `KubeDB` Ops-manager operator watches the `FerretDBOpsRequest` CR. + +6. When it finds a `FerretDBOpsRequest` CR, it pauses the `FerretDB` object which is referred from the `FerretDBOpsRequest`. So, the `KubeDB` Provisioner operator doesn't perform any operations on the `FerretDB` 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 `FerretDBOpsRequest` 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 `FerretDB` object to reflect the updated state. + +9. After the successful scaling of the `FerretDB` replicas, the `KubeDB` Ops-manager operator resumes the `FerretDB` 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 FerretDB using `FerretDBOpsRequest` CRD. \ No newline at end of file diff --git a/docs/guides/ferretdb/scaling/vertical-scaling/_index.md b/docs/guides/ferretdb/scaling/vertical-scaling/_index.md new file mode 100644 index 0000000000..2597e01702 --- /dev/null +++ b/docs/guides/ferretdb/scaling/vertical-scaling/_index.md @@ -0,0 +1,10 @@ +--- +title: Vertical Scaling +menu: + docs_{{ .version }}: + identifier: fr-vertical-scaling + name: Vertical Scaling + parent: fr-scaling + weight: 20 +menu_name: docs_{{ .version }} +--- \ No newline at end of file diff --git a/docs/guides/ferretdb/scaling/vertical-scaling/overview.md b/docs/guides/ferretdb/scaling/vertical-scaling/overview.md new file mode 100644 index 0000000000..f5d5f369c1 --- /dev/null +++ b/docs/guides/ferretdb/scaling/vertical-scaling/overview.md @@ -0,0 +1,54 @@ +--- +title: FerretDB Vertical Scaling Overview +menu: + docs_{{ .version }}: + identifier: fr-vertical-scaling-overview + name: Overview + parent: fr-vertical-scaling + weight: 10 +menu_name: docs_{{ .version }} +section_menu_id: guides +--- + +> New to KubeDB? Please start [here](/docs/README.md). + +# FerretDB 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 `FerretDB`. + +## Before You Begin + +- You should be familiar with the following `KubeDB` concepts: + - [FerretDB](/docs/guides/ferretdb/concepts/ferretdb.md) + - [FerretDBOpsRequest](/docs/guides/ferretdb/concepts/opsrequest.md) + +## How Vertical Scaling Process Works + +The following diagram shows how KubeDB Ops-manager operator updates the resources of the `FerretDB`. Open the image in a new tab to see the enlarged version. + +
+  Vertical scaling process of FerretDB +
Fig: Vertical scaling process of FerretDB
+
+ +The vertical scaling process consists of the following steps: + +1. At first, a user creates a `FerretDB` Custom Resource (CR). + +2. `KubeDB` Provisioner operator watches the `FerretDB` CR. + +3. When the operator finds a `FerretDB` 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 `FerretDB`, the user creates a `FerretDBOpsRequest` CR with desired information. + +5. `KubeDB` Ops-manager operator watches the `FerretDBOpsRequest` CR. + +6. When it finds a `FerretDBOpsRequest` CR, it pauses the `FerretDB` object which is referred from the `FerretDBOpsRequest`. So, the `KubeDB` Provisioner operator doesn't perform any operations on the `FerretDB` 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 `FerretDB` object to reflect the updated state. + +9. After the successful update of the `FerretDB` resources, the `KubeDB` Ops-manager operator resumes the `FerretDB` 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 FerretDB `FerretDBOpsRequest` CRD. \ No newline at end of file diff --git a/docs/guides/ferretdb/scaling/vertical-scaling/vertical-ops.md b/docs/guides/ferretdb/scaling/vertical-scaling/vertical-ops.md new file mode 100644 index 0000000000..b22b46ea70 --- /dev/null +++ b/docs/guides/ferretdb/scaling/vertical-scaling/vertical-ops.md @@ -0,0 +1,282 @@ +--- +title: Vertical Scaling FerretDB +menu: + docs_{{ .version }}: + identifier: fr-vertical-scaling-ops + name: VerticalScaling OpsRequest + parent: fr-vertical-scaling + weight: 20 +menu_name: docs_{{ .version }} +section_menu_id: guides +--- + +> New to KubeDB? Please start [here](/docs/README.md). + +# Vertical Scale FerretDB + +This guide will show you how to use `KubeDB` Ops-manager operator to update the resources of a FerretDB. + +## 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: + - [FerretDB](/docs/guides/ferretdb/concepts/ferretdb.md) + - [FerretDBOpsRequest](/docs/guides/ferretdb/concepts/opsrequest.md) + - [Vertical Scaling Overview](/docs/guides/ferretdb/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/ferretdb](/docs/examples/ferretdb) directory of [kubedb/docs](https://github.com/kubedb/docs) repository. + +## Apply Vertical Scaling on FerretDB + +Here, we are going to deploy a `FerretDB` using a supported version by `KubeDB` operator. Then we are going to apply vertical scaling on it. + +### Prepare FerretDB + +Now, we are going to deploy a `FerretDB` with version `1.23.0`. + +### Deploy FerretDB + +In this section, we are going to deploy a FerretDB. Then, in the next section we will update the resources using `FerretDBOpsRequest` CRD. Below is the YAML of the `FerretDB` CR that we are going to create, + +```yaml +apiVersion: kubedb.com/v1alpha2 +kind: FerretDB +metadata: + name: fr-vertical + namespace: demo +spec: + version: "1.23.0" + replicas: 1 + backend: + externallyManaged: false + storage: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 500Mi + deletionPolicy: WipeOut +``` + +Let's create the `FerretDB` CR we have shown above, + +```bash +$ kubectl create -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/ferretdb/scaling/fr-vertical.yaml +ferretdb.kubedb.com/fr-vertical created +``` + +Now, wait until `fr-vertical` has status `Ready`. i.e, + +```bash +$ kubectl get fr -n demo +NAME TYPE VERSION STATUS AGE +fr-vertical kubedb.com/v1alpha2 1.23.0 Ready 17s +``` + +Let's check the Pod containers resources, + +```bash +$ kubectl get pod -n demo fr-vertical-0 -o json | jq '.spec.containers[].resources' +{ + "limits": { + "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 `FerretDBOpsRequest` CR to update the resources of this ferretdb. + +### Vertical Scaling + +Here, we are going to update the resources of the ferretdb to meet the desired resources after scaling. + +#### Create FerretDBOpsRequest + +In order to update the resources of the ferretdb, we have to create a `FerretDBOpsRequest` CR with our desired resources. Below is the YAML of the `FerretDBOpsRequest` CR that we are going to create, + +```yaml +apiVersion: ops.kubedb.com/v1alpha1 +kind: FerretDBOpsRequest +metadata: + name: ferretdb-scale-vertical + namespace: demo +spec: + type: VerticalScaling + databaseRef: + name: fr-vertical + verticalScaling: + node: + 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 `fr-vertical` ferretdb. +- `spec.type` specifies that we are performing `VerticalScaling` on our database. +- `spec.VerticalScaling.standalone` specifies the desired resources after scaling. +- Have a look [here](/docs/guides/ferretdb/concepts/opsrequest.md) on the respective sections to understand the `timeout` & `apply` fields. + +Let's create the `FerretDBOpsRequest` CR we have shown above, + +```bash +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/ferretdb/scaling/vertical-scaling/fr-vertical-ops.yaml +ferretdbopsrequest.ops.kubedb.com/ferretdb-scale-vertical created +``` + +#### Verify FerretDB resources updated successfully + +If everything goes well, `KubeDB` Ops-manager operator will update the resources of `FerretDB` object and related `PetSet` and `Pods`. + +Let's wait for `FerretDBOpsRequest` to be `Successful`. Run the following command to watch `FerretDBOpsRequest` CR, + +```bash +$ kubectl get ferretdbopsrequest -n demo +Every 2.0s: kubectl get ferretdbopsrequest -n demo +NAME TYPE STATUS AGE +ferretdb-scale-vertical VerticalScaling Successful 44s +``` + +We can see from the above output that the `FerretDBOpsRequest` has succeeded. If we describe the `FerretDBOpsRequest` we will get an overview of the steps that were followed to scale the ferretdb. + +```bash +$ kubectl describe ferretdbopsrequest -n demo ferretdb-scale-vertical +Name: ferretdb-scale-vertical +Namespace: demo +Labels: +Annotations: +API Version: ops.kubedb.com/v1alpha1 +Kind: FerretDBOpsRequest +Metadata: + Creation Timestamp: 2024-10-21T12:25:33Z + Generation: 1 + Resource Version: 366310 + UID: 38631646-684f-4c2a-8496-c7b085743243 +Spec: + Apply: IfReady + Database Ref: + Name: fr-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-10-21T12:25:33Z + Message: FerretDB ops-request has started to vertically scaling the FerretDB nodes + Observed Generation: 1 + Reason: VerticalScaling + Status: True + Type: VerticalScaling + Last Transition Time: 2024-10-21T12:25:36Z + Message: Successfully paused database + Observed Generation: 1 + Reason: DatabasePauseSucceeded + Status: True + Type: DatabasePauseSucceeded + Last Transition Time: 2024-10-21T12:25:37Z + Message: Successfully updated PetSets Resources + Observed Generation: 1 + Reason: UpdatePetSets + Status: True + Type: UpdatePetSets + Last Transition Time: 2024-10-21T12:25:42Z + Message: get pod; ConditionStatus:True; PodName:fr-vertical-0 + Observed Generation: 1 + Status: True + Type: GetPod--fr-vertical-0 + Last Transition Time: 2024-10-21T12:25:42Z + Message: evict pod; ConditionStatus:True; PodName:fr-vertical-0 + Observed Generation: 1 + Status: True + Type: EvictPod--fr-vertical-0 + Last Transition Time: 2024-10-21T12:25:47Z + Message: check pod running; ConditionStatus:True; PodName:fr-vertical-0 + Observed Generation: 1 + Status: True + Type: CheckPodRunning--fr-vertical-0 + Last Transition Time: 2024-10-21T12:25:52Z + Message: Successfully Restarted Pods With Resources + Observed Generation: 1 + Reason: RestartPods + Status: True + Type: RestartPods + Last Transition Time: 2024-10-21T12:25:52Z + Message: Successfully completed the VerticalScaling for FerretDB + Observed Generation: 1 + Reason: Successful + Status: True + Type: Successful + Observed Generation: 1 + Phase: Successful +Events: + Type Reason Age From Message + ---- ------ ---- ---- ------- + Normal Starting 58s KubeDB Ops-manager Operator Start processing for FerretDBOpsRequest: demo/ferretdb-scale-vertical + Normal Starting 58s KubeDB Ops-manager Operator Pausing FerretDB database: demo/fr-vertical + Normal Successful 58s KubeDB Ops-manager Operator Successfully paused FerretDB database: demo/fr-vertical for FerretDBOpsRequest: ferretdb-scale-vertical + Normal UpdatePetSets 54s KubeDB Ops-manager Operator Successfully updated PetSets Resources + Warning get pod; ConditionStatus:True; PodName:fr-vertical-0 49s KubeDB Ops-manager Operator get pod; ConditionStatus:True; PodName:fr-vertical-0 + Warning evict pod; ConditionStatus:True; PodName:fr-vertical-0 49s KubeDB Ops-manager Operator evict pod; ConditionStatus:True; PodName:fr-vertical-0 + Warning check pod running; ConditionStatus:True; PodName:fr-vertical-0 44s KubeDB Ops-manager Operator check pod running; ConditionStatus:True; PodName:fr-vertical-0 + Normal RestartPods 39s KubeDB Ops-manager Operator Successfully Restarted Pods With Resources + Normal Starting 39s KubeDB Ops-manager Operator Resuming FerretDB database: demo/fr-vertical + Normal Successful 39s KubeDB Ops-manager Operator Successfully resumed FerretDB database: demo/fr-vertical for FerretDBOpsRequest: ferretdb-scale-vertical +``` + +Now, we are going to verify from the Pod yaml whether the resources of the ferretdb has updated to meet up the desired state, Let's check, + +```bash +$ kubectl get pod -n demo fr-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 FerretDB. + +## Cleaning Up + +To clean up the Kubernetes resources created by this tutorial, run: + +```bash +kubectl delete fr -n demo fr-vertical +kubectl delete ferretdbopsrequest -n demo ferretdb-scale-vertical +``` \ No newline at end of file diff --git a/docs/guides/ferretdb/tls/_index.md b/docs/guides/ferretdb/tls/_index.md new file mode 100644 index 0000000000..aabd98c8c5 --- /dev/null +++ b/docs/guides/ferretdb/tls/_index.md @@ -0,0 +1,10 @@ +--- +title: Run FerretDB with TLS +menu: + docs_{{ .version }}: + identifier: fr-tls + name: TLS/SSL Encryption + parent: fr-ferretdb-guides + weight: 45 +menu_name: docs_{{ .version }} +--- diff --git a/docs/guides/ferretdb/tls/configure_tls.md b/docs/guides/ferretdb/tls/configure_tls.md new file mode 100644 index 0000000000..82480afc9b --- /dev/null +++ b/docs/guides/ferretdb/tls/configure_tls.md @@ -0,0 +1,244 @@ +--- +title: FerretDB TLS/SSL Encryption +menu: + docs_{{ .version }}: + identifier: fr-tls-configure + name: FerretDB TLS/SSL Configuration + parent: fr-tls + weight: 20 +menu_name: docs_{{ .version }} +section_menu_id: guides +--- + +> New to KubeDB? Please start [here](/docs/README.md). + +# Run FerretDB with TLS/SSL (Transport Encryption) + +KubeDB supports providing TLS/SSL encryption (via, `sslMode`) for FerretDB. This tutorial will show you how to use KubeDB to run a FerretDB database with TLS/SSL encryption. + +## 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 [`cert-manger`](https://cert-manager.io/docs/installation/) v1.0.0 or later to your cluster to manage your SSL/TLS certificates. + +- 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/ferretdb](https://github.com/kubedb/docs/tree/{{< param "info.version" >}}/docs/examples/ferretdb) folder in GitHub repository [kubedb/docs](https://github.com/kubedb/docs). + +## Overview + +KubeDB uses following crd fields to enable SSL/TLS encryption in Mongodb. + +- `spec:` + - `sslMode` + - `tls:` + - `issuerRef` + - `certificate` + +Read about the fields in details in [ferretdb concept](/docs/guides/ferretdb/concepts/ferretdb.md), + +`sslMode` enables TLS/SSL or mixed TLS/SSL used for all network connections. The value of `sslMode` field can be one of the following: + +| Value | Description | +| :----------: | :----------------------------------------------------------------------------------------------------------------------------- | +| `disabled` | The server does not use TLS/SSL. | +| `requireSSL` | The server uses and accepts only TLS/SSL encrypted connections. | + +The specified ssl mode will be used by health checker and exporter of FerretDB. + +When, SSLMode is anything other than `disabled`, users must specify the `tls.issuerRef` field. KubeDB uses the `issuer` or `clusterIssuer` referenced in the `tls.issuerRef` field, and the certificate specs provided in `tls.certificate` to generate certificate secrets. These certificate secrets are then used to generate required certificates including `ca.pem`, `tls.crt` and `tls.key`. + +## Create Issuer/ ClusterIssuer + +We are going to create an example `Issuer` that will be used throughout the duration of this tutorial to enable SSL/TLS in FerretDB. Alternatively, you can follow this [cert-manager tutorial](https://cert-manager.io/docs/configuration/ca/) to create your own `Issuer`. + +- Start off by generating you ca certificates using openssl. + +```bash +openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout ./ca.key -out ./ca.crt -subj "/CN=ferretdb/O=kubedb" +``` + +- Now create a ca-secret using the certificate files you have just generated. + +```bash +kubectl create secret tls ferretdb-ca \ + --cert=ca.crt \ + --key=ca.key \ + --namespace=demo +``` + +Now, create an `Issuer` using the `ca-secret` you have just created. The `YAML` file looks like this: + +```yaml +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: ferretdb-ca-issuer + namespace: demo +spec: + ca: + secretName: ferretdb-ca +``` + +Apply the `YAML` file: + +```bash +$ kubectl create -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/ferretdb/tls/issuer.yaml +issuer.cert-manager.io/ferretdb-ca-issuer created +``` + +## TLS/SSL encryption in FerretDB + +Below is the YAML for FerretDB with TLS enabled. Backend Postgres will automatically managed by KubeDB: + +```yaml +apiVersion: kubedb.com/v1alpha2 +kind: FerretDB +metadata: + name: fr-tls + namespace: demo +spec: + version: "1.23.0" + authSecret: + externallyManaged: false + storage: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 500Mi + backend: + externallyManaged: false + deletionPolicy: WipeOut + replicas: 1 + sslMode: requireSSL + tls: + issuerRef: + apiGroup: "cert-manager.io" + kind: Issuer + name: ferretdb-ca-issuer +``` + +### Deploy FerretDB + +```bash +$ kubectl create -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/ferretdb/tls/ferretdb-tls.yaml +ferretdb.kubedb.com/fr-tls created +``` + +Now, wait until `fr-tls created` has status `Ready`. i.e, + +```bash +$ watch kubectl get fr -n demo +Every 2.0s: kubectl get ferretdb -n demo +NAME TYPE VERSION STATUS AGE +fr-tls kubedb.com/v1alpha2 1.23.0 Ready 60s +``` + +### Verify TLS/SSL in FerretDB + +Now, connect to this database through [mongosh](https://www.mongodb.com/docs/mongodb-shell/) and verify if `SSLMode` has been set up as intended (i.e, `require`). + +```bash +$ kubectl describe secret -n demo fr-tls-client-cert +Name: fr-tls-client-cert +Namespace: demo +Labels: app.kubernetes.io/component=database + app.kubernetes.io/instance=fr-tls + app.kubernetes.io/managed-by=kubedb.com + app.kubernetes.io/name=ferretdbs.kubedb.com + controller.cert-manager.io/fao=true +Annotations: cert-manager.io/alt-names: + cert-manager.io/certificate-name: fr-tls-client-cert + cert-manager.io/common-name: fr-tls + cert-manager.io/ip-sans: + cert-manager.io/issuer-group: cert-manager.io + cert-manager.io/issuer-kind: Issuer + cert-manager.io/issuer-name: ferretdb-ca-issuer + cert-manager.io/subject-organizationalunits: client + cert-manager.io/subject-organizations: kubedb + cert-manager.io/uri-sans: + +Type: kubernetes.io/tls + +Data +==== +ca.crt: 1155 bytes +tls.crt: 1176 bytes +tls.key: 1679 bytes +``` + +Now we need save the client cert and key to two different files and make a pem file. +Additionally, to verify server, we need to store ca.crt. + +```bash +$ kubectl get secrets -n demo fr-tls-client-cert -o jsonpath='{.data.tls\.crt}' | base64 -d > client.crt +$ kubectl get secrets -n demo fr-tls-client-cert -o jsonpath='{.data.tls\.key}' | base64 -d > client.key +$ kubectl get secrets -n demo fr-tls-client-cert -o jsonpath='{.data.ca\.crt}' | base64 -d > ca.crt +$ cat client.crt client.key > client.pem +``` + +Now, we can connect to our FerretDB with these files with mongosh client. + +```bash +$ kubectl get secrets -n demo fr-tls-auth -o jsonpath='{.data.\username}' | base64 -d +postgres +$ kubectl get secrets -n demo fr-tls-auth -o jsonpath='{.data.\\password}' | base64 -d +l*jGp8u*El8WRSDJ + +$ kubectl port-forward svc/fr-tls -n demo 27017 +Forwarding from 127.0.0.1:27017 -> 27018 +Forwarding from [::1]:27017 -> 27018 +Handling connection for 27017 +Handling connection for 27017 +``` + +Now in another terminal + +```bash +$ mongosh 'mongodb://postgres:l*jGp8u*El8WRSDJ@localhost:27017/ferretdb?authMechanism=PLAIN&tls=true&tlsCertificateKeyFile=./client.pem&tlsCaFile=./ca.crt' +Current Mongosh Log ID: 65efeea2a3347fff66d04c70 +Connecting to: mongodb://@localhost:27017/ferretdb?authMechanism=PLAIN&directConnection=true&serverSelectionTimeoutMS=2000&appName=mongosh+2.1.5 +Using MongoDB: 7.0.42 +Using Mongosh: 2.1.5 + +For mongosh info see: https://docs.mongodb.com/mongodb-shell/ + +------ + The server generated these startup warnings when booting + 2024-03-12T05:56:50.979Z: Powered by FerretDB v1.23.0 and PostgreSQL 13.13 on x86_64-pc-linux-musl, compiled by gcc. + 2024-03-12T05:56:50.979Z: Please star us on GitHub: https://github.com/FerretDB/FerretDB. + 2024-03-12T05:56:50.979Z: The telemetry state is undecided. + 2024-03-12T05:56:50.979Z: Read more about FerretDB telemetry and how to opt out at https://beacon.ferretdb.io. +------ + +ferretdb> +``` + +So our connection is now tls encrypted. + +## Cleaning up + +To clean up the Kubernetes resources created by this tutorial, run: + +```bash +kubectl delete ferretdb -n demo fr-tls +kubectl delete issuer -n demo ferretdb-ca-issuer +kubectl delete ns demo +``` + +## Next Steps + +- Detail concepts of [FerretDB object](/docs/guides/ferretdb/concepts/ferretdb.md). +- Detail concepts of [FerretDBVersion object](/docs/guides/ferretdb/concepts/catalog.md). +- Monitor your FerretDB database with KubeDB using [out-of-the-box Prometheus operator](/docs/guides/ferretdb/monitoring/using-prometheus-operator.md). +- Monitor your FerretDB database with KubeDB using [out-of-the-box builtin-Prometheus](/docs/guides/ferretdb/monitoring/using-builtin-prometheus.md). +- Want to hack on KubeDB? Check our [contribution guidelines](/docs/CONTRIBUTING.md). diff --git a/docs/guides/ferretdb/tls/overview.md b/docs/guides/ferretdb/tls/overview.md new file mode 100644 index 0000000000..09f50ed00d --- /dev/null +++ b/docs/guides/ferretdb/tls/overview.md @@ -0,0 +1,70 @@ +--- +title: FerretDB TLS/SSL Encryption Overview +menu: + docs_{{ .version }}: + identifier: fr-tls-overview + name: Overview + parent: fr-tls + weight: 10 +menu_name: docs_{{ .version }} +section_menu_id: guides +--- + +> New to KubeDB? Please start [here](/docs/README.md). + +# FerretDB TLS/SSL Encryption + +**Prerequisite :** To configure TLS/SSL in `FerretDB`, `KubeDB` uses `cert-manager` to issue certificates. So first you have to make sure that the cluster has `cert-manager` installed. To install `cert-manager` in your cluster following steps [here](https://cert-manager.io/docs/installation/kubernetes/). + +To issue a certificate, the following crd of `cert-manager` is used: + +- `Issuer/ClusterIssuer`: Issuers, and ClusterIssuers represent certificate authorities (CAs) that are able to generate signed certificates by honoring certificate signing requests. All cert-manager certificates require a referenced issuer that is in a ready condition to attempt to honor the request. You can learn more details [here](https://cert-manager.io/docs/concepts/issuer/). + +- `Certificate`: `cert-manager` has the concept of Certificates that define a desired x509 certificate which will be renewed and kept up to date. You can learn more details [here](https://cert-manager.io/docs/concepts/certificate/). + +**FerretDB CRD Specification :** + +KubeDB uses following crd fields to enable SSL/TLS encryption in `FerretDB`. + +- `spec:` + - `sslMode` + - `tls:` + - `issuerRef` + - `certificates` + - `cientAuthMode` + Read about the fields in details from [ferretdb concept](/docs/guides/ferretdb/concepts/ferretdb.md), + +When, `sslMode` is set to `require`, the users must specify the `tls.issuerRef` field. `KubeDB` uses the `issuer` or `clusterIssuer` referenced in the `tls.issuerRef` field, and the certificate specs provided in `tls.certificate` to generate certificate secrets using `Issuer/ClusterIssuers` specification. These certificates secrets including `ca.crt`, `tls.crt` and `tls.key` etc. are used to configure `FerretDB` server, exporter etc. respectively. + +## How TLS/SSL configures in FerretDB + +The following figure shows how `KubeDB` enterprise used to configure TLS/SSL in FerretDB. Open the image in a new tab to see the enlarged version. + +
+Deploy FerretDB with TLS/SSL +
Fig: Deploy FerretDB with TLS/SSL
+
+ +Deploying FerretDB with TLS/SSL configuration process consists of the following steps: + +1. At first, a user creates a `Issuer/ClusterIssuer` cr. + +2. Then the user creates a `FerretDB` cr which refers to the `Issuer/ClusterIssuer` cr that the user created in the previous step. + +3. `KubeDB` Provisioner operator watches for the `FerretDB` cr. + +4. When it finds one, it creates `Secret`, `Service`, etc. for the `FerretDB` database. + +5. `KubeDB` Ops-manager operator watches for `FerretDB`(5c), `Issuer/ClusterIssuer`(5b), `Secret` and `Service`(5a). + +6. When it finds all the resources(`FerretDB`, `Issuer/ClusterIssuer`, `Secret`, `Service`), it creates `Certificates` by using `tls.issuerRef` and `tls.certificates` field specification from `FerretDB` cr. + +7. `cert-manager` watches for certificates. + +8. When it finds one, it creates certificate secrets `tls-secrets`(server, client, exporter secrets etc.) that holds the actual certificate signed by the CA. + +9. `KubeDB` Provisioner operator watches for the Certificate secrets `tls-secrets`. + +10. When it finds all the tls-secret, it creates the related `StatefulSets` so that FerretDB database can be configured with TLS/SSL. + +In the next doc, we are going to show a step-by-step guide on how to configure a `FerretDB` database with TLS/SSL. \ No newline at end of file diff --git a/docs/guides/ferretdb/update-version/_index.md b/docs/guides/ferretdb/update-version/_index.md new file mode 100644 index 0000000000..e0266958ab --- /dev/null +++ b/docs/guides/ferretdb/update-version/_index.md @@ -0,0 +1,10 @@ +--- +title: Updating FerretDB +menu: + docs_{{ .version }}: + identifier: fr-updating + name: UpdateVersion + parent: fr-ferretdb-guides + weight: 42 +menu_name: docs_{{ .version }} +--- \ No newline at end of file diff --git a/docs/guides/ferretdb/update-version/overview.md b/docs/guides/ferretdb/update-version/overview.md new file mode 100644 index 0000000000..0b20a2e9b9 --- /dev/null +++ b/docs/guides/ferretdb/update-version/overview.md @@ -0,0 +1,54 @@ +--- +title: Updating FerretDB Overview +menu: + docs_{{ .version }}: + identifier: fr-updating-overview + name: Overview + parent: fr-updating + weight: 10 +menu_name: docs_{{ .version }} +section_menu_id: guides +--- + +> New to KubeDB? Please start [here](/docs/README.md). + +# updating FerretDB version Overview + +This guide will give you an overview on how KubeDB Ops-manager operator update the version of `FerretDB`. + +## Before You Begin + +- You should be familiar with the following `KubeDB` concepts: + - [FerretDB](/docs/guides/ferretdb/concepts/ferretdb.md) + - [FerretDBOpsRequest](/docs/guides/ferretdb/concepts/opsrequest.md) + +## How update version Process Works + +The following diagram shows how KubeDB Ops-manager operator used to update the version of `FerretDB`. Open the image in a new tab to see the enlarged version. + +
+  updating Process of FerretDB +
Fig: updating Process of FerretDB
+
+ +The updating process consists of the following steps: + +1. At first, a user creates a `FerretDB` Custom Resource (CR). + +2. `KubeDB` Provisioner operator watches the `FerretDB` CR. + +3. When the operator finds a `FerretDB` 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 `FerretDB` the user creates a `FerretDBOpsRequest` CR with the desired version. + +5. `KubeDB` Ops-manager operator watches the `FerretDBOpsRequest` CR. + +6. When it finds a `FerretDBOpsRequest` CR, it halts the `FerretDB` object which is referred from the `FerretDBOpsRequest`. So, the `KubeDB` Provisioner operator doesn't perform any operations on the `FerretDB` object during the updating process. + +7. By looking at the target version from `FerretDBOpsRequest` 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 `FerretDB` object to reflect the updated state of the database. + +9. After successfully updating of `FerretDB` object, the `KubeDB` Ops-manager operator resumes the `FerretDB` 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 FerretDB using updateVersion operation. \ No newline at end of file diff --git a/docs/guides/ferretdb/update-version/update-version.md b/docs/guides/ferretdb/update-version/update-version.md new file mode 100644 index 0000000000..d84da9d671 --- /dev/null +++ b/docs/guides/ferretdb/update-version/update-version.md @@ -0,0 +1,241 @@ +--- +title: Updating FerretDB +menu: + docs_{{ .version }}: + identifier: fr-updating-ferretdb + name: Update version + parent: fr-updating + weight: 20 +menu_name: docs_{{ .version }} +section_menu_id: guides +--- + +> New to KubeDB? Please start [here](/docs/README.md). + +# update version of FerretDB + +This guide will show you how to use `KubeDB` Ops-manager operator to update the version of `FerretDB`. + +## 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: + - [FerretDB](/docs/guides/ferretdb/concepts/ferretdb.md) + - [FerretDBOpsRequest](/docs/guides/ferretdb/concepts/opsrequest.md) + - [Updating Overview](/docs/guides/ferretdb/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/ferretdb](/docs/examples/ferretdb) directory of [kubedb/docs](https://github.com/kube/docs) repository. + +### Prepare FerretDB + +Now, we are going to deploy a `FerretDB` =with version `1.18.0`. + +### Deploy FerretDB: + +In this section, we are going to deploy a FerretDB. Then, in the next section we will update the version using `FerretDBOpsRequest` CRD. Below is the YAML of the `FerretDB` CR that we are going to create, + +```yaml +apiVersion: kubedb.com/v1alpha2 +kind: FerretDB +metadata: + name: fr-update + namespace: demo +spec: + version: "1.18.0" + replicas: 1 + backend: + externallyManaged: false + storage: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 500Mi + deletionPolicy: WipeOut +``` + +Let's create the `FerretDB` CR we have shown above, + +```bash +$ kubectl create -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/ferretdb/update-version/fr-update.yaml +ferretdb.kubedb.com/fr-update created +``` + +Now, wait until `fr-update` created has status `Ready`. i.e, + +```bash +$ kubectl get fr -n demo + NAME TYPE VERSION STATUS AGE + fr-update kubedb.com/v1alpha2 1.18.0 Ready 26s +``` + +We are now ready to apply the `FerretDBOpsRequest` CR to update this FerretDB. + +### update FerretDB Version + +Here, we are going to update `FerretDB` from `1.18.0` to `1.23.0`. + +#### Create FerretDBOpsRequest: + +In order to update the FerretDB, we have to create a `FerretDBOpsRequest` CR with your desired version that is supported by `KubeDB`. Below is the YAML of the `FerretDBOpsRequest` CR that we are going to create, + +```yaml +apiVersion: ops.kubedb.com/v1alpha1 +kind: FerretDBOpsRequest +metadata: + name: ferretdb-version-update + namespace: demo +spec: + type: UpdateVersion + databaseRef: + name: fr-update + updateVersion: + targetVersion: 1.23.0 +``` + +Here, + +- `spec.databaseRef.name` specifies that we are performing operation on `fr-update` FerretDB. +- `spec.type` specifies that we are going to perform `UpdateVersion` on our FerretDB. +- `spec.updateVersion.targetVersion` specifies the expected version of the FerretDB `1.23.0`. + + +Let's create the `FerretDBOpsRequest` CR we have shown above, + +```bash +$ kubectl apply -f https://github.com/kubedb/docs/raw/{{< param "info.version" >}}/docs/examples/ferretdb/update-version/frops-update.yaml +ferretdbopsrequest.ops.kubedb.com/ferretdb-version-update created +``` + +#### Verify FerretDB version updated successfully : + +If everything goes well, `KubeDB` Ops-manager operator will update the image of `FerretDB` object and related `PetSets` and `Pods`. + +Let's wait for `FerretDBOpsRequest` to be `Successful`. Run the following command to watch `FerretDBOpsRequest` CR, + +```bash +$ watch kubectl get ferretdbopsrequest -n demo +Every 2.0s: kubectl get ferretdbopsrequest -n demo +NAME TYPE STATUS AGE +ferretdb-version-update UpdateVersion Successful 93s +``` + +We can see from the above output that the `FerretDBOpsRequest` has succeeded. If we describe the `FerretDBOpsRequest` we will get an overview of the steps that were followed to update the FerretDB. + +```bash +$ kubectl describe ferretdbopsrequest -n demo ferretdb-version-update +Name: ferretdb-version-update +Namespace: demo +Labels: +Annotations: +API Version: ops.kubedb.com/v1alpha1 +Kind: FerretDBOpsRequest +Metadata: + Creation Timestamp: 2024-10-21T05:06:17Z + Generation: 1 + Resource Version: 324860 + UID: 30d486a6-a8fe-4d82-a8b3-f13e299ef035 +Spec: + Apply: IfReady + Database Ref: + Name: fr-update + Type: UpdateVersion + Update Version: + Target Version: 1.23.0 +Status: + Conditions: + Last Transition Time: 2024-10-21T05:06:17Z + Message: FerretDB ops-request has started to update version + Observed Generation: 1 + Reason: UpdateVersion + Status: True + Type: UpdateVersion + Last Transition Time: 2024-10-21T05:06:25Z + Message: successfully reconciled the FerretDB with updated version + Observed Generation: 1 + Reason: UpdatePetSets + Status: True + Type: UpdatePetSets + Last Transition Time: 2024-10-21T05:06:30Z + Message: get pod; ConditionStatus:True; PodName:fr-update-0 + Observed Generation: 1 + Status: True + Type: GetPod--fr-update-0 + Last Transition Time: 2024-10-21T05:06:30Z + Message: evict pod; ConditionStatus:True; PodName:fr-update-0 + Observed Generation: 1 + Status: True + Type: EvictPod--fr-update-0 + Last Transition Time: 2024-10-21T05:06:35Z + Message: check pod running; ConditionStatus:True; PodName:fr-update-0 + Observed Generation: 1 + Status: True + Type: CheckPodRunning--fr-update-0 + Last Transition Time: 2024-10-21T05:06:40Z + Message: Successfully Restarted FerretDB pods + Observed Generation: 1 + Reason: RestartPods + Status: True + Type: RestartPods + Last Transition Time: 2024-10-21T05:06:40Z + Message: Successfully updated FerretDB + Observed Generation: 1 + Reason: UpdateDatabase + Status: True + Type: UpdateDatabase + Last Transition Time: 2024-10-21T05:06:40Z + Message: Successfully updated FerretDB version + Observed Generation: 1 + Reason: Successful + Status: True + Type: Successful + Observed Generation: 1 + Phase: Successful +Events: + Type Reason Age From Message + ---- ------ ---- ---- ------- + Normal Starting 59s KubeDB Ops-manager Operator Start processing for FerretDBOpsRequest: demo/ferretdb-version-update + Normal Starting 59s KubeDB Ops-manager Operator Pausing FerretDB database: demo/fr-update + Normal Successful 59s KubeDB Ops-manager Operator Successfully paused FerretDB database: demo/fr-update for FerretDBOpsRequest: ferretdb-version-update + Normal UpdatePetSets 51s KubeDB Ops-manager Operator successfully reconciled the FerretDB with updated version + Warning get pod; ConditionStatus:True; PodName:fr-update-0 46s KubeDB Ops-manager Operator get pod; ConditionStatus:True; PodName:fr-update-0 + Warning evict pod; ConditionStatus:True; PodName:fr-update-0 46s KubeDB Ops-manager Operator evict pod; ConditionStatus:True; PodName:fr-update-0 + Warning check pod running; ConditionStatus:True; PodName:fr-update-0 41s KubeDB Ops-manager Operator check pod running; ConditionStatus:True; PodName:fr-update-0 + Normal RestartPods 36s KubeDB Ops-manager Operator Successfully Restarted FerretDB pods + Normal Starting 36s KubeDB Ops-manager Operator Resuming FerretDB database: demo/fr-update + Normal Successful 36s KubeDB Ops-manager Operator Successfully resumed FerretDB database: demo/fr-update for FerretDBOpsRequest: ferretdb-version-update +``` + +Now, we are going to verify whether the `FerretDB` and the related `PetSets` their `Pods` have the new version image. Let's check, + +```bash +$ kubectl get fr -n demo fr-update -o=jsonpath='{.spec.version}{"\n"}' +1.23.0 + +$ kubectl get petset -n demo fr-update -o=jsonpath='{.spec.template.spec.containers[0].image}{"\n"}' +ghcr.io/appscode-images/ferretdb:1.23.0 + +$ kubectl get pods -n demo fr-update-0 -o=jsonpath='{.spec.containers[0].image}{"\n"}' +ghcr.io/appscode-images/ferretdb:1.23.0 +``` + +You can see from above, our `FerretDB` 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 fr -n demo fr-update +kubectl delete ferretdbopsrequest -n demo ferretdb-version-update +``` \ No newline at end of file diff --git a/docs/guides/mongodb/concepts/mongodb.md b/docs/guides/mongodb/concepts/mongodb.md index f49613080c..6677392182 100644 --- a/docs/guides/mongodb/concepts/mongodb.md +++ b/docs/guides/mongodb/concepts/mongodb.md @@ -366,7 +366,7 @@ The following fields are configurable in the `spec.tls` section: - `uris` (optional) is a list of URI Subject Alternative Names to be set in the Certificate. - `emailAddresses` (optional) is a list of email Subject Alternative Names to be set in the Certificate. - `privateKey` (optional) specifies options to control private keys used for the Certificate. - - `encoding` (optional) is the private key cryptography standards (PKCS) encoding for this certificate's private key to be encoded in. If provided, allowed values are "pkcs1" and "pkcs8" standing for PKCS#1 and PKCS#8, respectively. It defaults to PKCS#1 if not specified. + - `encoding` (optional) is the private key cryptography standards (PKCS) encoding for this certificate's private key to be encoded in. If provided, allowed values are "pkcs1" and "pkcs8" standing for PKCS#1 and PKCS#8, respectively. It defaults to PKCS#1 if not specified. ### spec.clusterAuthMode diff --git a/docs/guides/pgpool/concepts/pgpool.md b/docs/guides/pgpool/concepts/pgpool.md index b86b8c2c91..d125d4ed8a 100644 --- a/docs/guides/pgpool/concepts/pgpool.md +++ b/docs/guides/pgpool/concepts/pgpool.md @@ -236,7 +236,7 @@ The following fields are configurable in the `spec.tls` section: - `uris` (optional) is a list of URI Subject Alternative Names to be set in the Certificate. - `emailAddresses` (optional) is a list of email Subject Alternative Names to be set in the Certificate. - `privateKey` (optional) specifies options to control private keys used for the Certificate. - - `encoding` (optional) is the private key cryptography standards (PKCS) encoding for this certificate's private key to be encoded in. If provided, allowed values are "pkcs1" and "pkcs8" standing for PKCS#1 and PKCS#8, respectively. It defaults to PKCS#1 if not specified. + - `encoding` (optional) is the private key cryptography standards (PKCS) encoding for this certificate's private key to be encoded in. If provided, allowed values are "pkcs1" and "pkcs8" standing for PKCS#1 and PKCS#8, respectively. It defaults to PKCS#1 if not specified. ### spec.monitor diff --git a/docs/guides/pgpool/update-version/update_version.md b/docs/guides/pgpool/update-version/update_version.md index 7466e0d713..a63c0c9485 100644 --- a/docs/guides/pgpool/update-version/update_version.md +++ b/docs/guides/pgpool/update-version/update_version.md @@ -228,9 +228,9 @@ $ kubectl get pp -n demo pp-update -o=jsonpath='{.spec.version}{"\n"}' 4.5.0 $ kubectl get petset -n demo pp-update -o=jsonpath='{.spec.template.spec.containers[0].image}{"\n"}' -mongo:4.0.5 +ghcr.io/appscode-images/pgpool2:4.5.0 -$ kubectl get pods -n demo mg-standalone-0 -o=jsonpath='{.spec.containers[0].image}{"\n"}' +$ kubectl get pods -n demo pp-update-0 -o=jsonpath='{.spec.containers[0].image}{"\n"}' ghcr.io/appscode-images/pgpool2:4.5.0@sha256:2697fcad9e11bdc704f6ae0fba85c4451c6b0243140aaaa33e719c3af548bda1 ``` diff --git a/docs/images/ferretdb/fr-builtin-prom-target.png b/docs/images/ferretdb/fr-builtin-prom-target.png new file mode 100644 index 0000000000000000000000000000000000000000..a483bb5faf5f2b8cbd97450a95e4a50e14023920 GIT binary patch literal 66375 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

hP8^i^S0g zvs-?io*5DGJmxi0L8)!|YpE4Atm0tp`4SjWZEmymW~xsP*p5;)i^gAx{~7kVNpxx( za!KM*OR+iMv%uFynb7t0bK&D$bFw+y*h=>=$+AP})ou<#$ zBvaj)1j?{yz9$3km#^9JJB4}UR1?k4`h&|$r8|SWo(?%nO?(#fG_d}3HuUV~q_Ve%{t?#|&8=?iT)Zv61NFA;9NL~?e5FXSf9<01vDdu!$9+wr)jMqaG~+Q` zY5TqffVhab?y2-Ay$g5m z_Jn)+VQ&$hZ%f@7bexYx+317<9pcaVgU~c^4`-4T`@M)b>{Tp`fL^Cvc7}%ybuzec zNTF=0ZR2oZJBYvRF~K6%A#if0l5z%Flh*NmDfVsBv$1$hDm`~*tm2@o{pp#|x#{}IL zXiq`~Tybt~j-k6#Fbi(3d+UHx;8?Q7j5yB~lNmlJaNrIW3nbsTZ(d0RJT891Gag9w zdEliNSAM2x$cD+%7TOQu&C2++fBA?S?aQKW8j7*jb;n`6`^=Jla-gj$26^v``64+` z8OM>>*PKLJV3^)&uF%3d!Lyb|r6@8k&`DTvgYoDt22O?=Wqi zQ{z`Acg~Gvub|LL=Q!QHZ?;>p&oEdSg+=(no$Q7lPPWds5IOMU@(-}t5E~k`r~M5} z6drr6Li3p8sx{9{{8i1*hY-6&h6u#5ogo*Gt^D)thro3d$RuW=2mJilobXW9Tk&I& z%EBE;1|dweUKhW(LdAw|cVB4=XM`n4r}(r*toloqD+!!#E*`&x;VEzb6uD`9#YGLU|jKg@AZ*;4Uic=i_m$1mCW49P+0Otyo=dBt@+D6L5~_=IlG z6Evx#?Dcrwu8yu6w4T&rLOhpqJcu;Xfw8LP1o2>C_%ks-+Yr!zJuX~2MChbEq~NJ< z(S)?SzoU5jk=A)_*@FA7^A{$LV&&_|K*q@hWC?36R(hlJrUz%UKCub-83yEWU$spE zlOPy<-Jha%6Q`5*jPx%`+=v?XE=|5o8M zYZZLXR3q^(#!cxbWQ+Q~qFNy_GdHhYYvR0%2XY zQqK@$iiLz+Sqr?zLhzM9n+ft8#InSaB(hfK9j$}oc0xB`PgeiW^A=vTHxz|6wP=sUOg>8H>f_NH+aD=g#5i77Acx~lLh zyiz7!YUb>X$qTJ_mh4A!8 zF;er`p%uXYHn;S6Cqta1(KH@MZda~C-qv)piRe1^=jg*$tki*GP^w{X;pQGCp={gVq6q?bGq8=~2q@wLX1Y4Y?ySZTS=Dn)LAI6Zp{X|PJ%S1_W8He!>&F)OWxyzJCbx{;w;oT zl|;Tm4BAA|#U+DRBJ}Yfyb((BYQMGpE&#-%PR*BDg$ydDe*6<|d*@{fC7qm77i{d~ zdzU0CIa-S{?N?ZO0@_)m!0xW9;>!B+F(neIxk6n(~LbREQ z^BkIpJ^G?Om#o1E2eZ9kFlFfpC>j^1pP~$2e)8noy33!ccbd{Ur@t`)R}w$GKk(gS znggs7ztdZjuT@&#Y0nwr)r{Lk?ll|C=Sg&U0J1pGZe+t%ZwJP4JLpQsm+TgW*^XAz zIO$KpZkTn6-ged>Zd=kOv8pg?K)C!*;hji3YG_|vPZ)Q1WhJfuTr*M!VfV74z#az&M5w8z{IIt))bLw_8kN61#1@X>%Q z!u#INA)O+D>TmjLV2w0+Ci$|=#P;2n+Isx7;Qhdrf}q8l_<}gLM0?6@%KBex>< zv0-qUOzVo0-}48D2veDwXw|&tbZbxQI4BoN4-XH0 zg1&DN_smH5-~e{qtJU2j6=&q}7;6RU*`g`dp&CVX1G^)ON zeGPs3HPIDjCRPdD8twm_{OzF_G3(eqBxb~<=kC%Db4=HD9zI0wTwYz8yhz(U*`Y&< zJHVYp^7_jkQ{~m<#N6Quxk650<&en8lM>r+zM=2-o2 zC#Rv;C0_Zn+5b}S%~Vbb39{z*5thWltmoirFmON Ktw_Zj{yzYA?o^`y literal 0 HcmV?d00001 diff --git a/docs/images/ferretdb/fr-compute-autoscaling.svg b/docs/images/ferretdb/fr-compute-autoscaling.svg new file mode 100644 index 0000000000..9a7e057654 --- /dev/null +++ b/docs/images/ferretdb/fr-compute-autoscaling.svg @@ -0,0 +1,4 @@ + + + +
PetSet's Pods
PetSet's Pods
FerretDB CR
1. Create
1. Create
Updated PetSet's Pods
Updated PetSet's Pods
FerretDB compute AutoScaler
FerretDB compute AutoSc...
KubeDB Provisioner Operator
KubeDB Provisioner O...
2. Watch
2. Watch
3. Create
3. Create
KubeDB AutoScaler Operator
KubeDB AutoScaler Op...
5. Watch
5. Watch
FerretDB OpsRequest
FerretDB OpsRequest
7. Create
7. Create
KubeDB OpsManager Operator
KubeDB OpsManager Op...
refers to
refers to
refers to
refers to
Recommendation
Recommendation
6. generate
6. generate
4. Create
4. Create
8. Watch
8. Watch
9. Scale
resources
9. Scale...
Text is not SVG - cannot display
\ No newline at end of file diff --git a/docs/images/ferretdb/fr-coreos-prom-target.png b/docs/images/ferretdb/fr-coreos-prom-target.png new file mode 100644 index 0000000000000000000000000000000000000000..86e6f18c39fc5f56407f824b2ac5f1915a489c25 GIT binary patch literal 137484 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�A~?AJ|QGJF0# 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%0DoBiZVo5GkBUzYigW`(gFM$5Obm?!pgsW7 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 z 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 literal 0 HcmV?d00001 diff --git a/docs/images/ferretdb/fr-horizontal-scaling.svg b/docs/images/ferretdb/fr-horizontal-scaling.svg new file mode 100644 index 0000000000..c7349f3849 --- /dev/null +++ b/docs/images/ferretdb/fr-horizontal-scaling.svg @@ -0,0 +1,4 @@ + + + +
1.Create FerretDB
1.Create Postgr...
2.Watch
2.Watch
3.Create
3.Create
4.Create
4.Initiate Upgr...
6.Pause
6.Pause
7.Update & Perform Checks
7.Update & Perform...
8.Update FerretDB
8.Update FerretDB
9.Resume
9.Resume
Upgrading stage
Upgrading stage
User
User
                Community            Operator
           PetSet
Statef...
5.Watch
5.Watch
            Enterprise            Operator
FerretDB OpsRequest
FerretDB Op...
FerretDB
Postgr...
Updated/New
PetSet
Upda...
refers to
refers to
Updated FerretDB
Upgrad...
Text is not SVG - cannot display
\ No newline at end of file diff --git a/docs/images/ferretdb/fr-reconfigure-tls.svg b/docs/images/ferretdb/fr-reconfigure-tls.svg new file mode 100644 index 0000000000..c7349f3849 --- /dev/null +++ b/docs/images/ferretdb/fr-reconfigure-tls.svg @@ -0,0 +1,4 @@ + + + +
1.Create FerretDB
1.Create Postgr...
2.Watch
2.Watch
3.Create
3.Create
4.Create
4.Initiate Upgr...
6.Pause
6.Pause
7.Update & Perform Checks
7.Update & Perform...
8.Update FerretDB
8.Update FerretDB
9.Resume
9.Resume
Upgrading stage
Upgrading stage
User
User
                Community            Operator
           PetSet
Statef...
5.Watch
5.Watch
            Enterprise            Operator
FerretDB OpsRequest
FerretDB Op...
FerretDB
Postgr...
Updated/New
PetSet
Upda...
refers to
refers to
Updated FerretDB
Upgrad...
Text is not SVG - cannot display
\ No newline at end of file diff --git a/docs/images/ferretdb/fr-tls.svg b/docs/images/ferretdb/fr-tls.svg new file mode 100644 index 0000000000..de4e284ced --- /dev/null +++ b/docs/images/ferretdb/fr-tls.svg @@ -0,0 +1,4 @@ + + + +            Enterprise            Operator              Community            Operator
service
se...
secret
se...
tls-secret
tls-secret
Cert- manager
Cert- ma...
PetSet
Statef...
Issuer/Cluster Issuer
Issuer...
FerretDB
FerretDB
Certificates
Certif...
User
User
2.Create
2.Create
1.Create
1.Create
5a.Watch
5a.Watch
3.Watch
3.Watch
4.Create
4.Create
5c.Watch
5c.Watch
6.Create
6.Create
7.Watch
7.Watch
uses
uses
8.Create
8.Create
9.Watch
9.Watch
10.Create
10.Create
5b.Watch
5b.Watch
refers to
refers to
Text is not SVG - cannot display
\ No newline at end of file diff --git a/docs/images/ferretdb/fr-update.svg b/docs/images/ferretdb/fr-update.svg new file mode 100644 index 0000000000..5c28780bf3 --- /dev/null +++ b/docs/images/ferretdb/fr-update.svg @@ -0,0 +1,4 @@ + + + +
1.Create FerretDB
1.Create Postgr...
2.Watch
2.Watch
3.Create
3.Create
4.Create
4.Initiate Upgr...
6.Pause
6.Pause
7.Update & Perform Checks
7.Update & Perform...
8.Upgrage FerretDB
8.Upgrage FerretDB
9.Resume
9.Resume
Upgrading stage
Upgrading stage
User
User
                Community            Operator
           PetSet
Statef...
5.Watch
5.Watch
            Enterprise            Operator
FerretDB OpsRequest
FerretDB Op...
FerretDB
Postgr...
Updated/New
PetSet
Upda...
refers to
refers to
Upgraded FerretDB
Upgrad...
Text is not SVG - cannot display
\ No newline at end of file diff --git a/docs/images/ferretdb/fr-vertical-scaling.svg b/docs/images/ferretdb/fr-vertical-scaling.svg new file mode 100644 index 0000000000..c7349f3849 --- /dev/null +++ b/docs/images/ferretdb/fr-vertical-scaling.svg @@ -0,0 +1,4 @@ + + + +
1.Create FerretDB
1.Create Postgr...
2.Watch
2.Watch
3.Create
3.Create
4.Create
4.Initiate Upgr...
6.Pause
6.Pause
7.Update & Perform Checks
7.Update & Perform...
8.Update FerretDB
8.Update FerretDB
9.Resume
9.Resume
Upgrading stage
Upgrading stage
User
User
                Community            Operator
           PetSet
Statef...
5.Watch
5.Watch
            Enterprise            Operator
FerretDB OpsRequest
FerretDB Op...
FerretDB
Postgr...
Updated/New
PetSet
Upda...
refers to
refers to
Updated FerretDB
Upgrad...
Text is not SVG - cannot display
\ No newline at end of file