Skip to content

Commit cdf5a7a

Browse files
authored
Merge pull request #200 from vshn/postgresql/restore-single-database
Add documentation for restoring a single database on vshnpostgresql
2 parents dcbe98b + f79d200 commit cdf5a7a

File tree

1 file changed

+182
-0
lines changed

1 file changed

+182
-0
lines changed

docs/modules/ROOT/pages/vshn-managed/postgresql/restore.adoc

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ spec:
3737
restore:
3838
claimName: pgsql-app1-prod # <1>
3939
backupName: pgsql-app1-prod-pk8k4-2023-03-01-16-52-02 # <2>
40+
recoveryTimeStamp: "2023-03-01T17:30:00+00:00" # <3>
4041
backup:
4142
schedule: '0 22 * * *'
4243
service:
@@ -53,6 +54,7 @@ spec:
5354
----
5455
<1> The name of the instance you want to restore from
5556
<2> The backup name you want to restore
57+
<3> Optional: Point-in-time recovery timestamp. Must be *after* the backup time (in this example, after `2023-03-01 16:52:02`). If omitted, restores to the latest available state.
5658
5759
[NOTE]
5860
====
@@ -64,3 +66,183 @@ The only difference is the backup information in `spec.parameters.restore`.
6466
6567
The deletion process of a restored instance is the same as for normal instance.
6668
Check out xref:vshn-managed/postgresql/delete.adoc[this guide] how to delete a PostgreSQL instance.
69+
70+
== Restore a single database
71+
72+
This procedure allows you to restore a specific database from a backup to a running VSHNPostgreSQL instance. This is useful when you only need to recover one database instead of restoring an entire instance.
73+
74+
=== Overview
75+
76+
The restore process involves the following steps:
77+
78+
1. Create a temporary VSHNPostgreSQL instance for the restore
79+
2. Restore the full backup into this temporary instance using the standard restore procedure above
80+
3. Copy the specific database from the temporary instance to your target instance
81+
4. Clean up by deleting the temporary instance
82+
83+
=== Prerequisites
84+
85+
* A running VSHNPostgreSQL instance (the target where you want to restore the database)
86+
* Access to the backup you want to restore from
87+
88+
=== Procedure
89+
90+
First, create a new temporary VSHNPostgreSQL instance and restore the full backup into it using the steps documented above in "Restore a Backup".
91+
92+
Next, use the following script to copy the specific database from the temporary instance to your target instance.
93+
94+
The script performs the following actions:
95+
96+
* Creates a temporary pod with the PostgreSQL client tools
97+
* Connects the pod to both the temporary instance (source) and target instance (destination)
98+
* Uses `pg_dump` to export the database from the temporary instance
99+
* Pipes the dump directly into the target instance
100+
* Displays table statistics before and after to verify the migration
101+
* Cleans up the temporary pod when finished
102+
103+
Run the script with the following parameters:
104+
105+
[source,bash]
106+
----
107+
./restore-single-db.sh <SECRET_NAME_FROM> <SECRET_NAME_TO> <NAMESPACE>
108+
----
109+
110+
Where:
111+
112+
* `SECRET_NAME_FROM`: The secret name of the temporary instance containing the restored backup
113+
* `SECRET_NAME_TO`: The secret name of the target instance where you want to restore the database
114+
* `NAMESPACE`: The claim namespace where both instances are provisioned
115+
116+
[source,bash]
117+
----
118+
#!/bin/bash
119+
120+
set -e
121+
122+
SECRET_NAME_FROM=$1
123+
if [[ -z "$SECRET_NAME_FROM" ]]; then
124+
read -rp "Enter the secret name to copy the database from: " SECRET_NAME_FROM
125+
fi
126+
if [[ -z "$SECRET_NAME_FROM" ]]; then
127+
echo "Error: secret name cannot be empty."
128+
exit 1
129+
fi
130+
131+
SECRET_NAME_TO=$2
132+
if [[ -z "$SECRET_NAME_TO" ]]; then
133+
read -rp "Enter the secret name to copy the database to: " SECRET_NAME_TO
134+
fi
135+
if [[ -z "$SECRET_NAME_TO" ]]; then
136+
echo "Error: secret name cannot be empty."
137+
exit 1
138+
fi
139+
140+
NAMESPACE=$3
141+
if [[ -z "$NAMESPACE" ]]; then
142+
read -rp "Enter the namespace to perform the action in: " NAMESPACE
143+
fi
144+
if [[ -z "$NAMESPACE" ]]; then
145+
echo "Error: Namespace cannot be empty."
146+
exit 1
147+
fi
148+
149+
DB_NAME=$(kubectl --namespace "$NAMESPACE" get secret "$SECRET_NAME_FROM" -ojson | jq -r '.data.POSTGRESQL_DB' | base64 -d)
150+
if [[ -z "$DB_NAME" ]]; then
151+
echo "Error: DB_NAME not found."
152+
exit 1
153+
fi
154+
155+
POD_NAME="postgresql-dump-pod-${RANDOM}"
156+
157+
echo "Creating PostgreSQL dump pod '$POD_NAME' in namespace '$NAMESPACE'..."
158+
159+
cat <<EOF | kubectl --namespace "$NAMESPACE" apply -f -
160+
apiVersion: v1
161+
kind: Pod
162+
metadata:
163+
creationTimestamp: null
164+
name: $POD_NAME
165+
spec:
166+
containers:
167+
- name: postgres
168+
image: postgres:17.6
169+
command:
170+
- tail
171+
- "-f"
172+
- /dev/null
173+
envFrom:
174+
- secretRef:
175+
name: $SECRET_NAME_FROM
176+
env:
177+
- name: TO_POSTGRESQL_HOST
178+
valueFrom:
179+
secretKeyRef:
180+
name: $SECRET_NAME_TO
181+
key: POSTGRESQL_HOST
182+
- name: TO_POSTGRESQL_DB
183+
valueFrom:
184+
secretKeyRef:
185+
name: $SECRET_NAME_TO
186+
key: POSTGRESQL_DB
187+
- name: TO_POSTGRESQL_USER
188+
valueFrom:
189+
secretKeyRef:
190+
name: $SECRET_NAME_TO
191+
key: POSTGRESQL_USER
192+
- name: TO_POSTGRESQL_PASSWORD
193+
valueFrom:
194+
secretKeyRef:
195+
name: $SECRET_NAME_TO
196+
key: POSTGRESQL_PASSWORD
197+
restartPolicy: Never
198+
EOF
199+
200+
echo "Waiting for pod '$POD_NAME' to be ready..."
201+
kubectl wait --for=condition=ready pod/"$POD_NAME" --namespace="$NAMESPACE" --timeout=300s
202+
203+
if [[ $? -ne 0 ]]; then
204+
echo "Error: Pod '$POD_NAME' did not become ready in time. Exiting."
205+
kubectl delete pod "$POD_NAME" --namespace="$NAMESPACE"
206+
exit 1
207+
fi
208+
209+
echo "Pod '$POD_NAME' is ready. Running pg_dump..."
210+
211+
212+
echo "Running pg_dump from pod '$POD_NAME' for database '$DB_NAME'..."
213+
214+
echo "Tables and their stats in old db"
215+
kubectl exec "$POD_NAME" --namespace="$NAMESPACE" -- \
216+
sh -c "PGPASSWORD=\${POSTGRESQL_PASSWORD} PGSSLMODE=disable psql -h \${POSTGRESQL_HOST} -U \${POSTGRESQL_USER} -d \${POSTGRESQL_DB} --tuples-only --command \
217+
\"SELECT relname AS table_name, n_live_tup AS row_estimate FROM pg_stat_user_tables ORDER BY n_live_tup DESC;\""
218+
219+
kubectl exec "$POD_NAME" --namespace="$NAMESPACE" -- \
220+
sh -xc "PGPASSWORD=\${POSTGRESQL_PASSWORD} PGSSLMODE=disable pg_dump -h \${POSTGRESQL_HOST} -U \${POSTGRESQL_USER} -d \${POSTGRESQL_DB} --clean --if-exists --no-owner 2>/dev/null \
221+
| PGPASSWORD=\${TO_POSTGRESQL_PASSWORD} PGSSLMODE=disable psql -h \${TO_POSTGRESQL_HOST} -U \${TO_POSTGRESQL_USER} -d \${TO_POSTGRESQL_DB}"
222+
223+
echo "Tables and their stats in new db"
224+
kubectl exec "$POD_NAME" --namespace="$NAMESPACE" -- \
225+
sh -c "PGPASSWORD=\${TO_POSTGRESQL_PASSWORD} PGSSLMODE=disable psql -h \${TO_POSTGRESQL_HOST} -U \${TO_POSTGRESQL_USER} -d \${TO_POSTGRESQL_DB} --tuples-only --command \
226+
\"ANALYZE;SELECT relname AS table_name, n_live_tup AS row_estimate FROM pg_stat_user_tables ORDER BY n_live_tup DESC;\""
227+
228+
229+
if [[ $? -ne 0 ]]; then
230+
echo "Error: pg_dump failed. Check pod logs for details."
231+
kubectl logs "$POD_NAME" --namespace="$NAMESPACE"
232+
echo "Pod '$POD_NAME' retained for debugging. Delete manually when done."
233+
exit 1
234+
fi
235+
236+
echo "Database '$DB_NAME' successfully migrated"
237+
238+
echo "Deleting pod '$POD_NAME'..."
239+
kubectl delete pod "$POD_NAME" --namespace="$NAMESPACE" --grace-period=0 --force
240+
241+
if [[ $? -eq 0 ]]; then
242+
echo "Pod '$POD_NAME' deleted successfully."
243+
else
244+
echo "Warning: Failed to delete pod '$POD_NAME'. You may need to delete it manually."
245+
fi
246+
----
247+
248+
After the script completes successfully, the database has been copied from the temporary instance to your target instance. You can now deprovision the temporary restore instance as it is no longer needed.

0 commit comments

Comments
 (0)