Skip to content

Commit

Permalink
document typical manual operations envx and create-adhoc-backup.sh ba…
Browse files Browse the repository at this point in the history
…sh script, prepare tagged release pipeline
  • Loading branch information
majodev committed Jan 8, 2025
1 parent fc033c4 commit f6ff8b8
Show file tree
Hide file tree
Showing 4 changed files with 356 additions and 14 deletions.
108 changes: 106 additions & 2 deletions .github/workflows/build-test-publish-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,8 @@ jobs:
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
labels: ${{ steps.meta.outputs.labels }}

release:
if: startsWith(github.ref, 'refs/tags/v') || (github.event_name == 'push' && github.ref == 'refs/heads/main')
release-chart:
if: startsWith(github.ref, 'refs/tags/v')

needs:
- build-test
Expand All @@ -124,3 +124,107 @@ jobs:
env:
CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
CR_SKIP_EXISTING: "true"


release-bins:
if: startsWith(github.ref, 'refs/tags/v')

needs:
- build-test
permissions:
contents: write
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Configure Git
run: |
git config user.name "$GITHUB_ACTOR"
git config user.email "[email protected]"
- name: Build the Docker image with binaries
run: |
docker build --target builder --file Dockerfile --tag ${IMAGE_NAME}-builder:${GITHUB_SHA:8} .
docker create --name builder ${IMAGE_NAME}-builder:${GITHUB_SHA:8}
mkdir -p dist
- name: Extract binaries from container
run: |
for binary in backup-ns-linux-amd64 backup-ns-linux-arm64 backup-ns-darwin-amd64 backup-ns-darwin-arm64; do
docker cp builder:/app/bin/$binary dist/ || exit 1
done
cp LICENSE dist/
- name: Create release archives
run: |
cd dist
for file in backup-ns-*; do
tar czf "${file}.tar.gz" "$file" LICENSE
done
- name: Prepare script archive
run: |
cp create-adhoc-backup.sh dist/
chmod +x dist/create-adhoc-backup.sh
cd dist
tar czf "create-adhoc-backup.sh.tar.gz" create-adhoc-backup.sh LICENSE
- name: Upload release artifacts
uses: softprops/action-gh-release@v1
with:
files: |
dist/backup-ns-linux-amd64.tar.gz
dist/backup-ns-linux-arm64.tar.gz
dist/backup-ns-darwin-amd64.tar.gz
dist/backup-ns-darwin-arm64.tar.gz
dist/create-adhoc-backup.sh.tar.gz
name: Release ${{ github.ref_name }}
body: |
## backup-ns ${{ github.ref_name }}
### Docker image
```bash
docker pull ghcr.io/allaboutapps/backup-ns:${{ github.ref_name }}
```
### Local Installation (Linux/amd64)
```bash
curl -Lo backup-ns.tar.gz https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/backup-ns-linux-amd64.tar.gz
tar xzf backup-ns.tar.gz
chmod +x backup-ns-linux-amd64
sudo mv backup-ns-linux-amd64 /usr/local/bin/backup-ns
rm backup-ns.tar.gz
```
### Local Installation (macOS/arm64)
```bash
curl -Lo backup-ns.tar.gz https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/backup-ns-darwin-arm64.tar.gz
tar xzf backup-ns.tar.gz
chmod +x backup-ns-darwin-arm64
sudo mv backup-ns-darwin-arm64 /usr/local/bin/backup-ns
rm backup-ns.tar.gz
```
### Adhoc backup script
```bash
curl -Lo create-adhoc-backup.sh.tar.gz https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/create-adhoc-backup.sh.tar.gz
tar xzf create-adhoc-backup.sh.tar.gz
chmod +x create-adhoc-backup.sh
sudo mv create-adhoc-backup.sh /usr/local/bin/
rm create-adhoc-backup.sh.tar.gz
```
draft: false
prerelease: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Stop container
if: always()
run: docker stop builder

- name: Cleanup
if: always()
run: docker rm builder
159 changes: 159 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@
- [Usage](#usage)
- [Install via static manifests](#install-via-static-manifests)
- [Install via helm](#install-via-helm)
- [Adhoc operations](#adhoc-operations)
- [Create a new adhoc backup job via the `create-adhoc-backup.sh` script](#create-a-new-adhoc-backup-job-via-the-create-adhoc-backupsh-script)
- [Adhoc backups and dumps via a local `backup-ns` cli and `kubectl envx`](#adhoc-backups-and-dumps-via-a-local-backup-ns-cli-and-kubectl-envx)
- [Trigger an adhoc backup job](#trigger-an-adhoc-backup-job)
- [Dump the postgres database on the live filesystem](#dump-the-postgres-database-on-the-live-filesystem)
- [Restore the current dump of the postgres database on the live filesystem](#restore-the-current-dump-of-the-postgres-database-on-the-live-filesystem)
- [Dump the mysql/mariadb database on the live filesystem](#dump-the-mysqlmariadb-database-on-the-live-filesystem)
- [Restore the current dump of the mysql/mariadb database on the live filesystem](#restore-the-current-dump-of-the-mysqlmariadb-database-on-the-live-filesystem)
- [Labels](#labels)
- [Concepts](#concepts)
- [Structure](#structure)
Expand Down Expand Up @@ -51,6 +59,157 @@ The global controller components must be deployed via static manifests.

See https://code.allaboutapps.at/backup-ns/ for the latest helm chart and [`charts/backup-ns/values.yaml`](charts/backup-ns/values.yaml) for the default values.

### Adhoc operations

Sometimes it is necessary to manually create a volume snapshot or to trigger database dumps and restores. This can be done by:
* by using the namespaced `backup` cronjob as template for creating a new k8s adhoc backup job and overwriting the new `ENV` vars or
* running the `backup-ns` cli tool locally to create a new adhoc backup job, also overwriting the `ENV` vars (based on the `backup` cronjob).

Here are some sample operations for accomblish that.

#### Create a new adhoc backup job via the `create-adhoc-backup.sh` script

```bash
# Install the create-adhoc-backup.sh bash script locally
# See https://github.com/allaboutapps/backup-ns/releases for cli installation instructions.

# Run the create adhoc backup script. This will assume that there is a `backup` cronjob in the kubectl context namespace that is used as a base.
./create-adhoc-backup.sh
# Creating adhoc backup in ns=go-starter-dev...
# Prepared backup command:
# kubectl create job --from=cronjob.batch/backup "backup-adhoc-2025-01-08-155614" -o yaml --dry-run=client -n "go-starter-dev" | yq eval '.spec.template.spec.containers[0].env += [{"name": "BAK_LABEL_VS_RETAIN", "value": "days"}]' - | yq eval '.spec.template.spec.containers[0].env += [{"name": "BAK_LABEL_VS_TYPE", "value": "adhoc"}]' - | kubectl apply -f -
# Ensuring there is no other backup job running within ns=go-starter-dev...
# Creating job/backup-adhoc-2025-01-08-155614 for ns=go-starter-dev...
# job.batch/backup-adhoc-2025-01-08-155614 created
# Follow logs with:
# kubectl logs -n go-starter-dev -f job/backup-adhoc-2025-01-08-155614
# Waiting for backup job/backup-adhoc-2025-01-08-155614 to complete for ns=go-starter-dev...
# job.batch/backup-adhoc-2025-01-08-155614 condition met

# List all snapshots in this namespace via:
# kubectl -n go-starter-dev get vs -lbackup-ns.sh/retain -Lbackup-ns.sh/type,backup-ns.sh/retain,backup-ns.sh/daily,backup-ns.sh/weekly,backup-ns.sh/monthly,backup-ns.sh/delete-after

# Adhoc backups are only kept for 30days by default, you can delete this auto-retention flag manually by running:
# kubectl -n go-starter-dev label vs/<snapshot-name> backup-ns.sh/retain- backup-ns.sh/delete-after-
```

#### Adhoc backups and dumps via a local `backup-ns` cli and `kubectl envx`

This requires the [`kubectl envx`](https://github.com/majodev/kubectl-envx) plugin to be installed and the `backup-ns` binary to be available locally (so it can interact with `kubectl` directly).

```bash
# Install the backup-ns binary locally
# See https://github.com/allaboutapps/backup-ns/releases for cli installation instructions.

# Install the kubectl envx plugin
kubectl krew envx install

# Show the current ENV vars of the backup-ns cronjob
kubectl envx cronjob/backup
# BAK_DB_POSTGRES=true
# BAK_FLOCK=true
# BAK_LABEL_VS_RETAIN=daily_weekly_monthly
# BAK_LABEL_VS_TYPE=cronjob
# BAK_NAMESPACE=go-starter-dev
# BAK_LABEL_VS_POD=backup
# TZ=Europe/Vienna
```

##### Trigger an adhoc backup job

```bash
# same ENV vars as the backup cronjob, but disabling flock and changing the type to adhoc and retain to days
kubectl envx cronjob/backup BAK_LABEL_VS_TYPE=adhoc BAK_FLOCK=false BAK_LABEL_VS_RETAIN=days -- ./backup-ns create
# 2025/01/08 16:43:08 VS Name: data-2025-01-08-164308-dcdkes
# 2025/01/08 16:43:08 Checking if PVC 'data' exists in namespace 'go-starter-dev'...
# 2025/01/08 16:43:08 PVC 'data' is available in namespace 'go-starter-dev'.
# 2025/01/08 16:43:08 Checking if resource 'deployment/app-base' exists in namespace 'go-starter-dev'...
# 2025/01/08 16:43:09 Resource 'deployment/app-base' is available in namespace 'go-starter-dev'.
# 2025/01/08 16:43:09 Checking if Postgres is available in namespace 'go-starter-dev'...
# 2025/01/08 16:43:10 Checking free space on /var/lib/postgresql/data in namespace 'go-starter-dev'...
# 2025/01/08 16:43:11 Free space check succeeded. Used: 2%, Threshold: 90%.
# 2025/01/08 16:43:14 Templated script 'postgres_dump.sh.tmpl' completed.
# 2025/01/08 16:43:14 Finished postgres dump in namespace='go-starter-dev'!
# 2025/01/08 16:43:14 Creating VolumeSnapshot 'data-2025-01-08-164308-dcdkes' in namespace 'go-starter-dev'...
# 2025/01/08 16:43:15 Waiting for VolumeSnapshot 'data-2025-01-08-164308-dcdkes' to be ready (timeout: 15m)...
# 2025/01/08 16:43:41 Finished backup vs_name='data-2025-01-08-164308-dcdkes' in namespace='go-starter-dev'!
```

##### Dump the postgres database on the live filesystem

```bash
kubectl envx cronjob/backup -- ./backup-ns postgres dump
# 2025/01/08 16:49:30 Checking if resource 'deployment/app-base' exists in namespace 'go-starter-dev'...
# 2025/01/08 16:49:31 Resource 'deployment/app-base' is available in namespace 'go-starter-dev'. Output:
# 2025/01/08 16:49:31 Checking if Postgres is available in namespace 'go-starter-dev'...
# 2025/01/08 16:49:32 Templated script 'postgres_check.sh.tmpl' completed.
# 2025/01/08 16:49:32 Checking free space on /var/lib/postgresql/data in namespace 'go-starter-dev'...
# 2025/01/08 16:49:33 Free space check succeeded. Used: 2%, Threshold: 90%. Output:
# 2025/01/08 16:49:33 Backing up Postgres database '${POSTGRES_DB}' in namespace 'go-starter-dev'...
# 2025/01/08 16:49:35 Templated script 'postgres_dump.sh.tmpl' completed. Output:
# + trap 'exit_code=$?; [ $exit_code -ne 0 ] && echo "TRAP!" && rm -f /var/lib/postgresql/data/dump.sql.gz && df -h /var/lib/postgresql/data; exit $exit_code' EXIT
# + trap 'trap - SIGTERM && kill -- -$$' SIGTERM SIGPIPE
# + pg_dump --username=dbuser --format=p --clean --if-exists go-starter-dev --host 127.0.0.1 --port 5432
# + gzip -c
# + ls -lha /var/lib/postgresql/data/dump.sql.gz
# + '[' -s /var/lib/postgresql/data/dump.sql.gz ']'
# -rw-r--r-- 1 postgres root 21.3K Jan 8 15:49 /var/lib/postgresql/data/dump.sql.gz
# + df -h /var/lib/postgresql/data
# Filesystem Size Used Available Use% Mounted on
# /dev/sdc 9.7G 177.4M 9.6G 2% /var/lib/postgresql/data
# + exit_code=0
# + '[' 0 -ne 0 ']'
# + exit 0
# 2025/01/08 16:49:35 Finished postgres dump in namespace='go-starter-dev'!
```

##### Restore the current dump of the postgres database on the live filesystem

```bash
kubectl envx cronjob/backup -- ./backup-ns postgres restore
# 2025/01/08 16:53:58 Checking if resource 'deployment/app-base' exists in namespace 'go-starter-dev'...
# 2025/01/08 16:53:58 Resource 'deployment/app-base' is available in namespace 'go-starter-dev'. Output:
# 2025/01/08 16:53:58 Checking if Postgres is available in namespace 'go-starter-dev'...
# 2025/01/08 16:53:59 Templated script 'postgres_check.sh.tmpl' completed.
# 2025/01/08 16:53:59 Restoring Postgres database '${POSTGRES_DB}' in namespace 'go-starter-dev'...
# 2025/01/08 16:54:00 Templated script 'postgres_restore.sh.tmpl' completed. Output:
# + '[' -s /var/lib/postgresql/data/dump.sql.gz ']'
# + ls -lha /var/lib/postgresql/data/dump.sql.gz
# -rw-r--r-- 1 postgres root 21.3K Jan 8 15:49 /var/lib/postgresql/data/dump.sql.gz
# + gzip -dc /var/lib/postgresql/data/dump.sql.gz
# + psql --host 127.0.0.1 --port 5432 --username=dbuser go-starter-dev
# SET
# [...]
# DROP INDEX
# ALTER TABLE
# DROP TABLE
# DROP SEQUENCE
# DROP TYPE
# DROP EXTENSION
# CREATE EXTENSION
# COMMENT
# CREATE TYPE
# ALTER TYPE
# CREATE TYPE
# ALTER TYPE
# SET
# [...]
# COPY 137
# 2025/01/08 16:54:00 Finished postgres restore in namespace='go-starter-dev'!
```

##### Dump the mysql/mariadb database on the live filesystem

```bash
kubectl envx cronjob/backup -- ./backup-ns mysql dump
```

##### Restore the current dump of the mysql/mariadb database on the live filesystem

```bash
kubectl envx cronjob/backup -- ./backup-ns mysql restore
```


### Labels

Expand Down
85 changes: 85 additions & 0 deletions create-adhoc-backup.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
#!/bin/bash
set -Eeo pipefail

# This script creates an adhoc backup job for the (current) namespace in kubectl context.
# $ BAK_DRY_RUN=true ./create-adhoc-backup.sh

# -------------
# env

# You can provide the following env vars to this script:
NAMESPACE="${NAMESPACE:=$( (kubectl config view --minify | grep namespace | cut -d" " -f6) || kubectl get sa -o=jsonpath='{.items[0]..metadata.namespace}' || echo "default")}"
BACKUP_JOB_WAIT="${BACKUP_JOB_WAIT:="true"}"
BACKUP_JOB_WAIT_TIMEOUT="${BACKUP_JOB_WAIT_TIMEOUT:="1h"}"

# BAK_* variables are injected as ENV into the job container, the following are the default overrides
# see https://git.allaboutapps.at/projects/AW/repos/backup-ns/browse/lib/bak_env.sh

# BAK_LABEL_VS_TYPE: adhoc (flag for adhoc backup job)
BAK_LABEL_VS_TYPE="${BAK_LABEL_VS_TYPE:="adhoc"}"
# BAK_LABEL_VS_RETAIN: days (flag for retention policy, currently only days is supported)
BAK_LABEL_VS_RETAIN="${BAK_LABEL_VS_RETAIN:="days"}"

# -------------
# main

echo "Creating adhoc backup in ns=${NAMESPACE}..."

# Check deps
command -v awk >/dev/null || fatal "awk is required but not found."
command -v yq >/dev/null || fatal "yq is required but not found."

# Collect BAK_* environment variables and construct yq commands to inject the explicit BAK_* env vars into the kubectl job definition
backup_env_vars=$(( set -o posix ; set ) | grep "BAK_" | awk -F= '{print $1}')
yq_cmd=""
while IFS= read -r bak_key; do
bak_value=$(eval "echo \$${bak_key}")
yq_cmd+=" | yq eval '.spec.template.spec.containers[0].env += [{\"name\": \"${bak_key}\", \"value\": \"${bak_value}\"}]' -"
done <<< "$backup_env_vars"
# create the command so we can print it right before executing it
timestamp=$(date +"%Y-%m-%d-%H%M%S")
backup_cmd="kubectl create job --from=cronjob.batch/backup \"backup-adhoc-${timestamp}\" -o yaml --dry-run=client -n \"${NAMESPACE}\" \
${yq_cmd} \
| kubectl apply -f -"
echo "Prepared backup command:"
echo "$backup_cmd"
echo "Ensuring there is no other backup job running within ns=${NAMESPACE}..."
if [ "$BACKUP_JOB_WAIT" == "true" ]; then
sleep 3
fi
# kubectl get job -l app=backup
# ensure there is currently no other backup job running, if that is the case, exit 1
if kubectl -n "${NAMESPACE}" get job -l app=backup -o jsonpath='{.items[*].status.active}' | grep -q "1"; then
>&2 echo "Another backup job is currently running, exit 1!"
exit 1
fi
# Create the backup job with the overwritten env vars
echo "Creating job/backup-adhoc-${timestamp} for ns=${NAMESPACE}..."
eval "$backup_cmd"
echo "Follow logs with:"
echo " kubectl logs -n ${NAMESPACE} -f job/backup-adhoc-${timestamp}"
if [ "$BACKUP_JOB_WAIT" == "true" ]; then
sleep 2
echo "Waiting for backup job/backup-adhoc-${timestamp} to complete for ns=${NAMESPACE}..."
kubectl -n "${NAMESPACE}" wait --for=condition=complete --timeout="$BACKUP_JOB_WAIT_TIMEOUT" "job/backup-adhoc-${timestamp}"
fi
echo ""
echo "List all snapshots in this namespace via:"
echo "kubectl -n ${NAMESPACE} get vs -lbackup-ns.sh/retain -Lbackup-ns.sh/type,backup-ns.sh/retain,backup-ns.sh/daily,backup-ns.sh/weekly,backup-ns.sh/monthly,backup-ns.sh/delete-after"
echo ""
echo "Adhoc backups are only kept for 30days by default, you can delete this auto-retention flag manually by running:"
echo "kubectl -n ${NAMESPACE} label vs/<snapshot-name> backup-ns.sh/retain- backup-ns.sh/delete-after-"
Loading

0 comments on commit f6ff8b8

Please sign in to comment.