Creating a YDB backup
Using the ydb-backup.sh script, a cluster administrator can create an Identity and Access Management database backup in YDB and save it on a workstation as a tar.gz archive.
The script is supported in Stackland version 26.3 and higher.
Getting started
-
Make sure the workstation (Linux or macOS) has
bash,awk,du,kubectl,tar, andmktempinstalled. -
Prepare a kubeconfig file with admin cluster access. You can use a kubeconfig with the
cluster-adminrole created when installing the cluster. -
Make sure your YDB is accessible and the workstation has enough free space to store both the unpacked data and the archive simultaneously.
-
Make sure a default
StorageClasswith file volume support (volumeMode: Filesystem) is configured in the cluster. The script uses this storage class for a temporary PVC with a full data copy. The storage must have enough free space for the PVC. -
Specify the absolute path to your kubeconfig file, then check the selected cluster and storage classes:
KUBECONFIG_PATH="<absolute_path_to_kubeconfig>" kubectl --kubeconfig "${KUBECONFIG_PATH}" config current-context kubectl --kubeconfig "${KUBECONFIG_PATH}" cluster-info kubectl --kubeconfig "${KUBECONFIG_PATH}" get storageclass
Creating a backup
-
Save the script below as
ydb-backup.shon your workstation.ydb-backup.sh script
#!/usr/bin/env bash set -Eeuo pipefail usage() { printf 'Usage: %s [--kubeconfig PATH] [--pvc-size SIZE] [--timeout SECONDS] [OUTPUT_PATH]\n' "$(basename -- "$0")" } kubeconfig_flag="" pvc_size="20Gi" timeout="3600" output_argument="" while (( $# > 0 )); do case "$1" in --kubeconfig) (( $# >= 2 )) || { printf 'Missing value for --kubeconfig\n' >&2 exit 2 } kubeconfig_flag="$2" shift 2 ;; --kubeconfig=*) kubeconfig_flag="${1#*=}" shift ;; --pvc-size) (( $# >= 2 )) || { printf 'Missing value for --pvc-size\n' >&2 exit 2 } pvc_size="$2" shift 2 ;; --pvc-size=*) pvc_size="${1#*=}" shift ;; --timeout) (( $# >= 2 )) || { printf 'Missing value for --timeout\n' >&2 exit 2 } timeout="$2" shift 2 ;; --timeout=*) timeout="${1#*=}" shift ;; -h|--help) usage exit 0 ;; -*) printf 'Unknown option: %s\n' "$1" >&2 usage >&2 exit 2 ;; *) [[ -z "${output_argument}" ]] || { printf 'Only one output path may be specified\n' >&2 usage >&2 exit 2 } output_argument="$1" shift ;; esac done if [[ -n "${kubeconfig_flag}" ]]; then resolved_kubeconfig_path="${kubeconfig_flag}" elif [[ -n "${KUBECONFIG:-}" ]]; then resolved_kubeconfig_path="${KUBECONFIG}" elif [[ -n "${HOME:-}" ]]; then resolved_kubeconfig_path="${HOME}/.kube/config" else printf 'Cannot resolve kubeconfig: HOME is not set\n' >&2 exit 2 fi readonly SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" readonly YDB_NAMESPACE="stackland-iam" readonly YDB_ENDPOINT="grpcs://ydb-storage-grpc.${YDB_NAMESPACE}.svc.cluster.local:2135" readonly YDB_DATABASE="/ydb-storage/iam" readonly YDB_USER="operator" readonly KUBECONFIG_PATH="${resolved_kubeconfig_path}" readonly PVC_SIZE="${pvc_size}" readonly TIMEOUT="${timeout}" readonly BACKUP_TIMESTAMP="$(date -u +%Y%m%dT%H%M%SZ)" readonly POD_NAME="ydb-backup" readonly PVC_NAME="ydb-backup" readonly OUTPUT_PATH="${output_argument:-${SCRIPT_DIR}/ydb-backup-${BACKUP_TIMESTAMP}.tar.gz}" POD_CREATED=false PVC_CREATED=false STAGING_DIR="" log() { printf '[ydb-backup] %s\n' "$*" } fail() { printf '[ydb-backup] ERROR: %s\n' "$*" >&2 exit 1 } cleanup() { local exit_code=$? trap - EXIT INT TERM if [[ "${POD_CREATED}" == true ]]; then log "Deleting Pod ${YDB_NAMESPACE}/${POD_NAME}" kubectl --kubeconfig "${KUBECONFIG_PATH}" \ --namespace "${YDB_NAMESPACE}" \ delete pod "${POD_NAME}" --ignore-not-found --wait=false >/dev/null || true fi if [[ "${PVC_CREATED}" == true ]]; then log "Deleting PVC ${YDB_NAMESPACE}/${PVC_NAME}" kubectl --kubeconfig "${KUBECONFIG_PATH}" \ --namespace "${YDB_NAMESPACE}" \ delete pvc "${PVC_NAME}" --ignore-not-found --wait=false >/dev/null || true fi if [[ -n "${STAGING_DIR}" && -d "${STAGING_DIR}" ]]; then rm -rf -- "${STAGING_DIR}" fi exit "${exit_code}" } trap cleanup EXIT trap 'exit 130' INT trap 'exit 143' TERM for command_name in kubectl tar mktemp; do command -v "${command_name}" >/dev/null 2>&1 || fail "Required command is not installed: ${command_name}" done [[ -f "${KUBECONFIG_PATH}" ]] || fail "Kubeconfig does not exist: ${KUBECONFIG_PATH}" [[ "${TIMEOUT}" =~ ^[1-9][0-9]*$ ]] || fail "Timeout must be a positive number of seconds" [[ "${PVC_SIZE}" =~ ^[1-9][0-9]*(Ei|Pi|Ti|Gi|Mi|Ki|E|P|T|G|M|K)?$ ]] \ || fail "PVC size must be a positive Kubernetes quantity, for example 20Gi" [[ ! -e "${OUTPUT_PATH}" ]] || fail "Output file already exists: ${OUTPUT_PATH}" YDB_IMAGE="$(kubectl --kubeconfig "${KUBECONFIG_PATH}" \ --namespace "${YDB_NAMESPACE}" \ get statefulset ydb-storage \ --output jsonpath='{.spec.template.spec.containers[0].image}')" [[ -n "${YDB_IMAGE}" ]] || fail "Cannot resolve image from StatefulSet ${YDB_NAMESPACE}/ydb-storage" readonly YDB_IMAGE output_directory="$(dirname -- "${OUTPUT_PATH}")" mkdir -p -- "${output_directory}" STAGING_DIR="$(mktemp -d "${output_directory}/.ydb-backup.XXXXXX")" log "Deleting stale Pod ${YDB_NAMESPACE}/${POD_NAME}" kubectl --kubeconfig "${KUBECONFIG_PATH}" \ --namespace "${YDB_NAMESPACE}" \ delete pod "${POD_NAME}" --ignore-not-found --wait=true --timeout="${TIMEOUT}s" >/dev/null log "Deleting stale PVC ${YDB_NAMESPACE}/${PVC_NAME}" kubectl --kubeconfig "${KUBECONFIG_PATH}" \ --namespace "${YDB_NAMESPACE}" \ delete pvc "${PVC_NAME}" --ignore-not-found --wait=true --timeout="${TIMEOUT}s" >/dev/null log "Creating ${PVC_SIZE} PVC ${YDB_NAMESPACE}/${PVC_NAME} using the default StorageClass" PVC_CREATED=true kubectl --kubeconfig "${KUBECONFIG_PATH}" apply -f - <<EOF apiVersion: v1 kind: PersistentVolumeClaim metadata: name: ${PVC_NAME} namespace: ${YDB_NAMESPACE} labels: app.kubernetes.io/name: ydb app.kubernetes.io/component: dump-download spec: accessModes: - ReadWriteOnce volumeMode: Filesystem resources: requests: storage: ${PVC_SIZE} EOF log "Creating Pod ${YDB_NAMESPACE}/${POD_NAME}" POD_CREATED=true kubectl --kubeconfig "${KUBECONFIG_PATH}" apply -f - <<EOF apiVersion: v1 kind: Pod metadata: name: ${POD_NAME} namespace: ${YDB_NAMESPACE} labels: app.kubernetes.io/name: ydb app.kubernetes.io/component: dump-download spec: automountServiceAccountToken: false restartPolicy: Never securityContext: runAsNonRoot: true runAsUser: 65532 runAsGroup: 65532 fsGroup: 65532 seccompProfile: type: RuntimeDefault containers: - name: backup image: ${YDB_IMAGE} imagePullPolicy: IfNotPresent command: - /bin/sleep - infinity env: - name: HOME value: /tmp securityContext: allowPrivilegeEscalation: false capabilities: drop: - ALL volumeMounts: - name: backup mountPath: /backup - name: ydb-ca mountPath: /credentials/ydb/ca.crt subPath: ca.crt readOnly: true - name: ydb-password mountPath: /credentials/ydb/password subPath: password readOnly: true volumes: - name: backup persistentVolumeClaim: claimName: ${PVC_NAME} - name: ydb-ca secret: secretName: ydb-storage-grpc-tls items: - key: ca.crt path: ca.crt - name: ydb-password secret: secretName: ydb-storage-operator-user items: - key: password path: password EOF log "Waiting for Pod ${YDB_NAMESPACE}/${POD_NAME}" kubectl --kubeconfig "${KUBECONFIG_PATH}" \ --namespace "${YDB_NAMESPACE}" \ wait --for=condition=Ready "pod/${POD_NAME}" --timeout="${TIMEOUT}s" >/dev/null log "Creating YDB dump in Pod ${POD_NAME}" if kubectl --kubeconfig "${KUBECONFIG_PATH}" \ --namespace "${YDB_NAMESPACE}" \ exec "${POD_NAME}" --container backup -- \ timeout "${TIMEOUT}s" \ /opt/ydb/bin/ydb \ --ca-file /credentials/ydb/ca.crt \ -e "${YDB_ENDPOINT}" \ -d "${YDB_DATABASE}" \ --user "${YDB_USER}" \ --password-file /credentials/ydb/password \ admin database dump \ --output /backup/database; then : else dump_exit_code=$? if (( dump_exit_code == 124 )); then fail "YDB dump timed out after ${TIMEOUT} seconds" fi fail "YDB dump failed with exit code ${dump_exit_code}" fi log "Downloading /backup/database" kubectl --kubeconfig "${KUBECONFIG_PATH}" \ --namespace "${YDB_NAMESPACE}" \ cp "${POD_NAME}:/backup/database" "${STAGING_DIR}/database" --container backup # wipe sensitive data kubectl --kubeconfig "${KUBECONFIG_PATH}" \ --namespace "${YDB_NAMESPACE}" \ exec "${POD_NAME}" --container backup -- \ timeout "${TIMEOUT}s" /bin/sh -eu -c ' command -v find >/dev/null command -v shred >/dev/null if [ -d /backup/database ]; then find /backup/database -type f -exec shred --iterations=1 --zero --exact -- {} + fi sync ' >/dev/null 2>&1 || true log "Creating ${OUTPUT_PATH}" tar -C "${STAGING_DIR}/database" -czf "${STAGING_DIR}/backup.tar.gz" . tar -tzf "${STAGING_DIR}/backup.tar.gz" >/dev/null \ || fail "Created backup archive is not readable" mv -- "${STAGING_DIR}/backup.tar.gz" "${OUTPUT_PATH}" archive_size="$(du -h "${OUTPUT_PATH}" | awk '{print $1}')" log "Backup completed: ${OUTPUT_PATH} (${archive_size})" -
Make the file executable:
chmod +x ydb-backup.sh -
Specify the path to the new archive and run the script:
BACKUP_PATH="$(pwd)/ydb-backup-$(date -u +%Y%m%dT%H%M%SZ).tar.gz" ./ydb-backup.sh \ --kubeconfig "${KUBECONFIG_PATH}" \ --pvc-size 20Gi \ --timeout 3600 \ "${BACKUP_PATH}"Where:
--kubeconfig: Kubeconfig file path. If not set, the script uses the path from theKUBECONFIGvariable, then falls back to$HOME/.kube/config. Specify the path to a single file.--pvc-size: Size of the temporary PVC for the full data copy in the cluster. The default value is20Gi. If the copy does not fit in the PVC, rerun the script with a larger value for this setting.--timeout: Timeout for individual operations and creating the YDB dump, in seconds. The default value is3600. This is not a limit on the total duration of the script execution.BACKUP_PATH: Path to the archive. An existing file will not be overwritten. If the path is not specified, the script generates aydb-backup-<UTC_date_and_time>.tar.gzfile in the script’s directory.
Do not run multiple instances of the script at once in the same cluster: they use the same temporary pod and PVC. After completion, the script deletes the temporary pod and PVC.
-
Wait for the
Backup completedmessage with the path to the archive and its size.
Checking the result
-
Make sure the archive is created and readable:
ls -lh "${BACKUP_PATH}" tar -tzf "${BACKUP_PATH}" >/dev/null -
Save the archive in a secure location separate from the cluster. The archive contains confidential Identity and Access Management data: restrict access to it during storage and transmission.
For information on how to restore the database, see Restoring a YDB from backup.