[Yandex Cloud documentation](../../../index.md) > [Yandex Cloud Stackland](../../index.md) > [Step-by-step guides](../index.md) > Users and groups > Creating a YDB backup

# Creating a YDB backup

Using the `ydb-backup.sh` script, a cluster administrator can create an [Identity and Access Management](../../concepts/components/iam.md) 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 {#prerequisites}

1. Make sure the workstation (Linux or macOS) has `bash`, `awk`, `du`, `kubectl`, `tar`, and `mktemp` installed.
1. Prepare a kubeconfig file with admin cluster access. You can use a kubeconfig with the `cluster-admin` role created [when installing the cluster](../../quickstart.md#installation-common).
1. Make sure your YDB is accessible and the workstation has enough free space to store both the unpacked data and the archive simultaneously.
1. Make sure a default `StorageClass` with 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.
1. Specify the absolute path to your kubeconfig file, then check the selected cluster and storage classes:

   ```bash
   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 {#create-backup}

1. Save the script below as `ydb-backup.sh` on your workstation.

   {% cut "ydb-backup.sh script" %}

   ```bash
   #!/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})"
   ```

   {% endcut %}

1. Make the file executable:

   ```bash
   chmod +x ydb-backup.sh
   ```

1. Specify the path to the new archive and run the script:

   ```bash
   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 the `KUBECONFIG` variable, 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 is `20Gi`. 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 is `3600`. 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 a `ydb-backup-<UTC_date_and_time>.tar.gz` file 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.

1. Wait for the `Backup completed` message with the path to the archive and its size.

## Checking the result {#check-result}

1. Make sure the archive is created and readable:

   ```bash
   ls -lh "${BACKUP_PATH}"
   tar -tzf "${BACKUP_PATH}" >/dev/null
   ```

1. 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](ydb-restore.md).