Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
---
kind:
- How To
products:
- Alauda Application Services
ProductsVersion:
- 4.0,4.1,4.2,4.3
id: KB260515008
---

# How to Deploy CloudBeaver for PostgreSQL Management

## Issue

You need a web-based SQL client for browsing and querying PostgreSQL instances managed by Alauda Application Services, without installing a desktop tool on every operator's workstation. CloudBeaver is the open-source web edition of DBeaver and runs as a single-pod Deployment on Kubernetes. This how-to deploys CloudBeaver and connects it to a PostgreSQL cluster managed by the PostgreSQL Operator.

## Environment

- Any Kubernetes cluster reachable to operators (NodePort exposure is used below; Ingress / Route works equally well)
- A `StorageClass` capable of provisioning ReadWriteOnce PVCs — used to persist CloudBeaver workspace state across pod restarts
- Network reachability from the CloudBeaver pod to the target PostgreSQL Service (`<cluster>` on port 5432)

## Resolution

### 1. Prepare the manifest

Save the following to `cloudbeaver.yaml`. Adjust `storageClassName`, image registry, and resource requests to match your environment:

```yaml
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: cloudbeaver
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
storageClassName: sc-topolvm # replace with any RWO StorageClass available in the cluster
volumeMode: Filesystem
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: cloudbeaver
spec:
replicas: 1
selector:
matchLabels:
app: cloudbeaver
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 1
template:
metadata:
labels:
app: cloudbeaver
spec:
containers:
- name: cloudbeaver
image: docker-mirrors.alauda.cn/dbeaver/cloudbeaver:latest
imagePullPolicy: Always
ports:
- name: web
containerPort: 8978
protocol: TCP
resources:
limits:
cpu: "1"
memory: 1Gi
requests:
cpu: 100m
memory: 256Mi
volumeMounts:
- name: cloudbeaver-data
mountPath: /opt/cloudbeaver/workspace
volumes:
- name: cloudbeaver-data
persistentVolumeClaim:
claimName: cloudbeaver
---
apiVersion: v1
kind: Service
metadata:
name: cloudbeaver
spec:
type: NodePort
selector:
app: cloudbeaver
ports:
- name: web
port: 8978
targetPort: 8978
protocol: TCP
```

> CloudBeaver stores its administrator password, saved connections, and query history under `/opt/cloudbeaver/workspace`. Without a persistent volume, everything is lost on every pod restart. Confirm the chosen `storageClassName` exists in the target cluster before applying.

> **Air-gapped / IPv6-only clusters:** the public `docker-mirrors.alauda.cn/dbeaver/cloudbeaver:latest` image may be unreachable from the cluster. Mirror it into the cluster's own registry first and reference that path in the Deployment, for example `skopeo copy docker://docker-mirrors.alauda.cn/dbeaver/cloudbeaver:latest docker://<cluster-registry>/dbeaver/cloudbeaver:latest`.

### 2. Deploy

```bash
kubectl -n <namespace> apply -f cloudbeaver.yaml
kubectl -n <namespace> rollout status deploy/cloudbeaver
```

### 3. Discover the access URL

The Service uses NodePort, so any node IP plus the allocated NodePort exposes the UI:

```bash
namespace=<namespace>
HOST=$(kubectl -n "$namespace" get pod -l app=cloudbeaver \
-o jsonpath='{.items[0].status.hostIP}')
PORT=$(kubectl -n "$namespace" get svc cloudbeaver \
-o jsonpath='{.spec.ports[0].nodePort}')
echo "http://$HOST:$PORT"
```

Open the URL in a browser.

### 4. Initial setup

1. On first launch CloudBeaver prompts you to set the administrator password. Choose a strong password and store it safely — this account governs all subsequent server-side configuration.
2. Log in with the administrator user.
3. (Optional) Switch the UI language under the user menu in the top-right.

### 5. Connect to a PostgreSQL instance

1. Click **New Connection** and choose **PostgreSQL**.
2. Fill **Host** with the cluster Service name and **Port** `5432`. From inside the cluster the host is `<cluster>.<namespace>` (the read-write Service); the `<cluster>-repl` Service points at replicas. From outside the cluster, retrieve the NodePort or LoadBalancer address:

```bash
kubectl -n <pg-namespace> get svc <cluster>
```

3. Enter the database user and password. The Operator stores each role's credentials in a Secret named `<role>.<cluster>.credentials.postgresql.acid.zalan.do`. Retrieve the `postgres` superuser password from its Secret:

```bash
kubectl get secret postgres.<cluster>.credentials.postgresql.acid.zalan.do \
-n <pg-namespace> -o jsonpath='{.data.password}' | base64 -d; echo
```

The same Secret also carries `username`, `host` and `port` keys.

4. (Optional) Set **Database** to the target database; otherwise CloudBeaver connects to the default `postgres` database.
5. Under **Access Management**, grant the current CloudBeaver user permission to use the new connection.
6. Save and test the connection. SQL editors can now be opened from the browser and queries executed against the PostgreSQL cluster.

### 6. Uninstall

```bash
kubectl -n <namespace> delete -f cloudbeaver.yaml
```

The PVC is removed alongside the rest of the manifest. To preserve CloudBeaver state for a future redeploy, delete only the Deployment and Service and re-attach the existing PVC during the next install.
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
---
kind:
- Solution
products:
- Alauda Application Services
ProductsVersion:
- 4.0,4.1,4.2,4.3
id: KB260515007
---

# How to Migrate a PostgreSQL Instance to Another Node

## Issue

A PostgreSQL cluster managed by the PostgreSQL Operator uses node-local storage
(for example TopoLVM). One or more instances must be moved off their current
node — because the node is being decommissioned, or an instance must run on a
specific compute node. Because the data lives in a node-local PersistentVolume,
the pod cannot simply be rescheduled; the volume is pinned to its node.

## Environment

- Alauda Application Services PostgreSQL Operator (Zalando-based,
`acid.zalan.do/v1` `postgresql` resource).
- Node-local storage such as TopoLVM (each PVC is bound to one node).
- At least one target node with enough free capacity. Because migration
re-clones data, the target node should have capacity for roughly **twice** the
instance's PVC size during the transition.

## Resolution

The migration relies on a property of the Operator: when an instance's PVC and
pod are deleted, the StatefulSet recreates the pod, a fresh PVC is provisioned
wherever the pod is scheduled, and Patroni re-clones the data from the current
leader. Data is preserved through streaming replication, not by moving the
volume.

> Validated on ACP 4.2 and 4.3: after deleting a replica's PVC and pod, the
> member was recreated on a node, re-synced from the leader, and previously
> written rows were present on the resynced member.

In the examples below set `$NAMESPACE` and `$CLUSTER_NAME` for the target
cluster. Replace placeholder node names with your own.

### 1. Confirm the current placement

```bash
kubectl get pod -n $NAMESPACE -o wide | grep $CLUSTER_NAME
kubectl get pvc -n $NAMESPACE -o wide | grep $CLUSTER_NAME
```

Note which member is the leader (do not delete the leader's volume first):

```bash
kubectl exec -n $NAMESPACE $CLUSTER_NAME-0 -c postgres -- patronictl list
```

### 2. Restrict scheduling to the target node(s)

So the recreated pod lands only on a desired node, cordon the other eligible
nodes (or use a `nodeSelector`/label that only the target nodes carry). Keep at
least the target node schedulable.

```bash
# Label ONLY the target node(s) so the recreated pod can land there and nowhere else
kubectl label node <target-node> target=true --overwrite
```

Do **not** label the source node — labeling both source and target would let the
pod reschedule back onto the source. If you prefer cordoning over labels, cordon
all non-target nodes instead and skip the `nodeSelector` step below.

If you use a label-based selector, set it on the instance:

```bash
kubectl patch postgresql -n $NAMESPACE $CLUSTER_NAME --type merge \
-p '{"spec":{"nodeSelector":{"target":"true"}}}'
```

### 3. Migrate one member at a time

Always migrate a **non-leader** member first. Delete its PVC and pod together —
the PVC deletion blocks until the pod that mounts it is gone, so delete the pod
in parallel:

```bash
# Delete the data PVC (it will stay in Terminating until the pod is gone)
kubectl delete pvc pgdata-$CLUSTER_NAME-1 -n $NAMESPACE --wait=false

# Delete the pod to release the PVC
kubectl delete pod $CLUSTER_NAME-1 -n $NAMESPACE
```

The StatefulSet recreates `$CLUSTER_NAME-1`; a new PVC is provisioned on the
scheduled node and Patroni re-clones from the leader.

### 4. Verify the member rejoined with its data

```bash
kubectl get pod -n $NAMESPACE -o wide | grep $CLUSTER_NAME-1
kubectl exec -n $NAMESPACE $CLUSTER_NAME-0 -c postgres -- patronictl list
```

The migrated member should return to role `Replica`, state `running`/`streaming`
with `Lag in MB` `0`. Spot-check data on the member (it is read-only / in
recovery):

```bash
kubectl exec -n $NAMESPACE $CLUSTER_NAME-1 -c postgres -- \
psql -U postgres -tAc "SELECT pg_is_in_recovery();" # expect t
```

### 5. Migrate the (former) leader if required

To move the leader, first perform a switchover so another member becomes leader,
then repeat steps 3–4 for the old leader:

```bash
kubectl exec -n $NAMESPACE $CLUSTER_NAME-0 -c postgres -- \
patronictl switchover $CLUSTER_NAME --force
```

### 6. Restore scheduling

Uncordon any nodes you cordoned and remove temporary labels/`nodeSelector` once
all members are on their intended nodes.

## Notes

- Migrate members one at a time and wait for each to fully re-sync (`Lag = 0`)
before moving the next, so the cluster always retains a healthy quorum.
- For a single-instance cluster, temporarily scale to two instances, let the new
member sync on the target node, switch over, then scale back to one — this
avoids downtime that a delete-and-reclone of the sole instance would cause.
Loading