small changes
All checks were successful
Check scripts syntax / check-scripts-syntax (push) Successful in 30s
All checks were successful
Check scripts syntax / check-scripts-syntax (push) Successful in 30s
This commit is contained in:
@@ -6,23 +6,24 @@ Kubernetes-focused shell scripts intended for cronjobs and operational utilities
|
|||||||
|
|
||||||
### `automated-nfs-backup.sh`
|
### `automated-nfs-backup.sh`
|
||||||
|
|
||||||
Backs up each top-level folder found in `NFS_SOURCE_PATH` into an encrypted `.7z` archive, with optional Kubernetes workload quiescing when a folder name exactly matches a namespace name.
|
Backs up each top-level folder found in `NFS_SOURCE_PATH` into a single encrypted `.7z` archive per run, with optional Kubernetes workload quiescing when a folder name exactly matches a namespace name.
|
||||||
|
|
||||||
Behavior:
|
Behavior:
|
||||||
- Exact folder-to-namespace mapping only.
|
- Exact folder-to-namespace mapping only.
|
||||||
- Unmapped folder: backup still runs, Kubernetes scale actions are skipped.
|
- Unmapped folder: backup still runs, Kubernetes scale actions are skipped.
|
||||||
- Mapped folder: saves replicas, scales selected workloads down, waits, runs backup, restores replicas, waits again.
|
- Mapped folder: saves replicas, scales selected workloads down, waits, adds that folder to the shared run archive, restores replicas, waits again.
|
||||||
- Scale-down issues are warnings by policy (backup still runs).
|
- Scale-down issues are warnings by policy (backup still runs).
|
||||||
- Restore issues are warnings by policy (run can still complete successfully).
|
- Restore issues are warnings by policy (run can still complete successfully).
|
||||||
|
- Cleanup can delete the last `N` archives ordered by date (oldest side), while keeping at least one archive.
|
||||||
|
|
||||||
## Environment Variables
|
## Environment Variables
|
||||||
|
|
||||||
Required:
|
Required:
|
||||||
- `NFS_SOURCE_PATH`: Root path containing folders to back up.
|
- `NFS_SOURCE_PATH`: Root path containing folders to back up.
|
||||||
- `BACKUP_OUTPUT_PATH`: Destination path for generated `.7z` archives.
|
- `BACKUP_OUTPUT_PATH`: Destination path for generated `.7z` archives.
|
||||||
- `BACKUP_PASSWORD`: Password used for 7z encryption.
|
|
||||||
|
|
||||||
Optional:
|
Optional:
|
||||||
|
- `BACKUP_PASSWORD` (default: empty): When set, archive uses password protection; when empty, archive is not encrypted.
|
||||||
- `KUBECTL_BIN` (default: `kubectl`)
|
- `KUBECTL_BIN` (default: `kubectl`)
|
||||||
- `KUBE_CONTEXT` (default: empty)
|
- `KUBE_CONTEXT` (default: empty)
|
||||||
- `WORKLOAD_KINDS` (default: `deployment,statefulset,replicaset,replicationcontroller`)
|
- `WORKLOAD_KINDS` (default: `deployment,statefulset,replicaset,replicationcontroller`)
|
||||||
@@ -30,7 +31,7 @@ Optional:
|
|||||||
- `ARCHIVE_TS_FORMAT` (default: `%Y%m%d_%H%M%S`)
|
- `ARCHIVE_TS_FORMAT` (default: `%Y%m%d_%H%M%S`)
|
||||||
- `SEVENZ_METHOD` (default: `lzma2`)
|
- `SEVENZ_METHOD` (default: `lzma2`)
|
||||||
- `SEVENZ_LEVEL` (default: `9`)
|
- `SEVENZ_LEVEL` (default: `9`)
|
||||||
- `SEVENZ_HEADER_ENCRYPT` (default: `on`)
|
- `SEVENZ_HEADER_ENCRYPT` (default: `on`, only applied when `BACKUP_PASSWORD` is set)
|
||||||
- `SEVENZ_THREADS` (default: `on`)
|
- `SEVENZ_THREADS` (default: `on`)
|
||||||
- `SCALE_TIMEOUT_SECONDS` (default: `600`)
|
- `SCALE_TIMEOUT_SECONDS` (default: `600`)
|
||||||
- `SCALE_RETRY_COUNT` (default: `3`)
|
- `SCALE_RETRY_COUNT` (default: `3`)
|
||||||
@@ -41,6 +42,7 @@ Optional:
|
|||||||
- `NOTIFY_FAILURE_URL` (default: empty, disabled)
|
- `NOTIFY_FAILURE_URL` (default: empty, disabled)
|
||||||
- `NOTIFY_TITLE` (default: `Kubernetes`)
|
- `NOTIFY_TITLE` (default: `Kubernetes`)
|
||||||
- `NOTIFY_ASSET` (default: `kube config`)
|
- `NOTIFY_ASSET` (default: `kube config`)
|
||||||
|
- `CLEANUP_DELETE_COUNT` (default: `5`)
|
||||||
|
|
||||||
Notification payload (success and failure):
|
Notification payload (success and failure):
|
||||||
|
|
||||||
@@ -67,6 +69,7 @@ Notification payload (success and failure):
|
|||||||
- Restore warnings/timeouts: logged and counted, script does not fail solely because of restore warnings.
|
- Restore warnings/timeouts: logged and counted, script does not fail solely because of restore warnings.
|
||||||
- If `NOTIFY_SUCCESS_URL` is set, success notification is sent at the end of a successful run.
|
- If `NOTIFY_SUCCESS_URL` is set, success notification is sent at the end of a successful run.
|
||||||
- If `NOTIFY_FAILURE_URL` is set, failure notification is sent when backup failures are detected.
|
- If `NOTIFY_FAILURE_URL` is set, failure notification is sent when backup failures are detected.
|
||||||
|
- On successful runs, cleanup removes the last `CLEANUP_DELETE_COUNT` archives by date ordering (oldest side), without deleting the final remaining archive.
|
||||||
- Final summary always logs:
|
- Final summary always logs:
|
||||||
- `processed`
|
- `processed`
|
||||||
- `mapped`
|
- `mapped`
|
||||||
|
|||||||
@@ -9,9 +9,9 @@ setup_exit_handling
|
|||||||
# Required configuration
|
# Required configuration
|
||||||
require_env "NFS_SOURCE_PATH"
|
require_env "NFS_SOURCE_PATH"
|
||||||
require_env "BACKUP_OUTPUT_PATH"
|
require_env "BACKUP_OUTPUT_PATH"
|
||||||
require_env "BACKUP_PASSWORD"
|
|
||||||
|
|
||||||
# Optional configuration
|
# Optional configuration
|
||||||
|
BACKUP_PASSWORD="${BACKUP_PASSWORD:-}"
|
||||||
KUBECTL_BIN="${KUBECTL_BIN:-kubectl}"
|
KUBECTL_BIN="${KUBECTL_BIN:-kubectl}"
|
||||||
KUBE_CONTEXT="${KUBE_CONTEXT:-}"
|
KUBE_CONTEXT="${KUBE_CONTEXT:-}"
|
||||||
WORKLOAD_KINDS="${WORKLOAD_KINDS:-deployment,statefulset,replicaset,replicationcontroller}"
|
WORKLOAD_KINDS="${WORKLOAD_KINDS:-deployment,statefulset,replicaset,replicationcontroller}"
|
||||||
@@ -29,6 +29,7 @@ NOTIFY_SUCCESS_URL="${NOTIFY_SUCCESS_URL:-}"
|
|||||||
NOTIFY_FAILURE_URL="${NOTIFY_FAILURE_URL:-}"
|
NOTIFY_FAILURE_URL="${NOTIFY_FAILURE_URL:-}"
|
||||||
NOTIFY_TITLE="${NOTIFY_TITLE:-Kubernetes}"
|
NOTIFY_TITLE="${NOTIFY_TITLE:-Kubernetes}"
|
||||||
NOTIFY_ASSET="${NOTIFY_ASSET:-kube config}"
|
NOTIFY_ASSET="${NOTIFY_ASSET:-kube config}"
|
||||||
|
CLEANUP_DELETE_COUNT="${CLEANUP_DELETE_COUNT:-5}"
|
||||||
|
|
||||||
KUBECTL_ARGS=()
|
KUBECTL_ARGS=()
|
||||||
if [[ -n "$KUBE_CONTEXT" ]]; then
|
if [[ -n "$KUBE_CONTEXT" ]]; then
|
||||||
@@ -78,6 +79,14 @@ validate_inputs() {
|
|||||||
die "NFS_SOURCE_PATH does not exist or is not a directory: ${NFS_SOURCE_PATH}"
|
die "NFS_SOURCE_PATH does not exist or is not a directory: ${NFS_SOURCE_PATH}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [[ ! "$CLEANUP_DELETE_COUNT" =~ ^[0-9]+$ ]]; then
|
||||||
|
die "CLEANUP_DELETE_COUNT must be an integer >= 0"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "$BACKUP_PASSWORD" ]]; then
|
||||||
|
log_warn "BACKUP_PASSWORD is empty. Archive will be created without password protection."
|
||||||
|
fi
|
||||||
|
|
||||||
mkdir -p "$BACKUP_OUTPUT_PATH"
|
mkdir -p "$BACKUP_OUTPUT_PATH"
|
||||||
mkdir -p "$TMP_STATE_DIR"
|
mkdir -p "$TMP_STATE_DIR"
|
||||||
|
|
||||||
@@ -250,11 +259,10 @@ wait_for_scale_state() {
|
|||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
archive_path_for_folder() {
|
archive_path_for_run() {
|
||||||
local folder_name="${1:?folder name required}"
|
|
||||||
local ts
|
local ts
|
||||||
ts="$(date +"$ARCHIVE_TS_FORMAT")"
|
ts="$(date +"$ARCHIVE_TS_FORMAT")"
|
||||||
printf '%s/%s_%s_%s.7z' "$BACKUP_OUTPUT_PATH" "$ARCHIVE_PREFIX" "$folder_name" "$ts"
|
printf '%s/%s_%s.7z' "$BACKUP_OUTPUT_PATH" "$ARCHIVE_PREFIX" "$ts"
|
||||||
}
|
}
|
||||||
|
|
||||||
json_escape() {
|
json_escape() {
|
||||||
@@ -304,25 +312,72 @@ EOF
|
|||||||
backup_folder() {
|
backup_folder() {
|
||||||
local folder_path="${1:?folder path required}"
|
local folder_path="${1:?folder path required}"
|
||||||
local archive_path="${2:?archive path required}"
|
local archive_path="${2:?archive path required}"
|
||||||
local password_arg="-p${BACKUP_PASSWORD}"
|
local -a cmd=(
|
||||||
|
7z a -t7z
|
||||||
|
"$archive_path"
|
||||||
|
"$folder_path"
|
||||||
|
"-m0=${SEVENZ_METHOD}"
|
||||||
|
"-mx=${SEVENZ_LEVEL}"
|
||||||
|
"-mmt=${SEVENZ_THREADS}"
|
||||||
|
)
|
||||||
|
|
||||||
7z a -t7z \
|
if [[ -n "$BACKUP_PASSWORD" ]]; then
|
||||||
"$archive_path" \
|
cmd+=("-p${BACKUP_PASSWORD}" "-mhe=${SEVENZ_HEADER_ENCRYPT}")
|
||||||
"$folder_path" \
|
fi
|
||||||
-m0="$SEVENZ_METHOD" \
|
|
||||||
-mx="$SEVENZ_LEVEL" \
|
"${cmd[@]}" >/dev/null
|
||||||
-mhe="$SEVENZ_HEADER_ENCRYPT" \
|
}
|
||||||
-mmt="$SEVENZ_THREADS" \
|
|
||||||
"$password_arg" \
|
cleanup_archives() {
|
||||||
>/dev/null
|
if (( CLEANUP_DELETE_COUNT == 0 )); then
|
||||||
|
log_info "Cleanup disabled (CLEANUP_DELETE_COUNT=0)"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
shopt -s nullglob
|
||||||
|
local archive_candidates=( "$BACKUP_OUTPUT_PATH"/"${ARCHIVE_PREFIX}"_*.7z )
|
||||||
|
shopt -u nullglob
|
||||||
|
|
||||||
|
local total_candidates="${#archive_candidates[@]}"
|
||||||
|
if (( total_candidates == 0 )); then
|
||||||
|
log_info "Cleanup skipped: no archives found for prefix '${ARCHIVE_PREFIX}' in ${BACKUP_OUTPUT_PATH}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
local sorted_archives=()
|
||||||
|
mapfile -t sorted_archives < <(ls -1t -- "${archive_candidates[@]}")
|
||||||
|
|
||||||
|
local total_sorted="${#sorted_archives[@]}"
|
||||||
|
if (( total_sorted <= 1 )); then
|
||||||
|
log_info "Cleanup skipped: only one archive present"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
local delete_count="$CLEANUP_DELETE_COUNT"
|
||||||
|
local max_deletable=$((total_sorted - 1))
|
||||||
|
if (( delete_count > max_deletable )); then
|
||||||
|
delete_count="$max_deletable"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if (( delete_count == 0 )); then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
local start_index=$((total_sorted - delete_count))
|
||||||
|
local i
|
||||||
|
for ((i = start_index; i < total_sorted; i++)); do
|
||||||
|
local archive_path="${sorted_archives[$i]}"
|
||||||
|
rm -f -- "$archive_path"
|
||||||
|
log_info "Cleanup deleted archive: ${archive_path}"
|
||||||
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
process_folder() {
|
process_folder() {
|
||||||
local folder_path="${1:?folder path is required}"
|
local folder_path="${1:?folder path is required}"
|
||||||
|
local archive_path="${2:?archive path is required}"
|
||||||
local folder_name
|
local folder_name
|
||||||
local namespace=""
|
local namespace=""
|
||||||
local state_file=""
|
local state_file=""
|
||||||
local archive_path=""
|
|
||||||
local has_mapping=0
|
local has_mapping=0
|
||||||
|
|
||||||
folder_name="$(basename "$folder_path")"
|
folder_name="$(basename "$folder_path")"
|
||||||
@@ -349,15 +404,9 @@ process_folder() {
|
|||||||
log_warn "No namespace matched folder '${folder_name}'. Running backup only."
|
log_warn "No namespace matched folder '${folder_name}'. Running backup only."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
archive_path="$(archive_path_for_folder "$folder_name")"
|
|
||||||
if backup_folder "$folder_path" "$archive_path"; then
|
if backup_folder "$folder_path" "$archive_path"; then
|
||||||
backup_successes=$((backup_successes + 1))
|
backup_successes=$((backup_successes + 1))
|
||||||
local file_size_bytes
|
log_info "Added folder '${folder_name}' to archive: ${archive_path}"
|
||||||
file_size_bytes="$(wc -c < "$archive_path" | tr -d '[:space:]')"
|
|
||||||
if [[ "$file_size_bytes" =~ ^[0-9]+$ ]]; then
|
|
||||||
((total_backup_size_bytes += file_size_bytes))
|
|
||||||
fi
|
|
||||||
log_info "Backup created: ${archive_path}"
|
|
||||||
else
|
else
|
||||||
backup_failures=$((backup_failures + 1))
|
backup_failures=$((backup_failures + 1))
|
||||||
log_error "Backup failed for folder '${folder_name}'"
|
log_error "Backup failed for folder '${folder_name}'"
|
||||||
@@ -385,16 +434,27 @@ main() {
|
|||||||
load_namespaces
|
load_namespaces
|
||||||
|
|
||||||
local folder_path
|
local folder_path
|
||||||
|
local run_archive_path=""
|
||||||
local found=0
|
local found=0
|
||||||
for folder_path in "${NFS_SOURCE_PATH}"/*; do
|
for folder_path in "${NFS_SOURCE_PATH}"/*; do
|
||||||
[[ -d "$folder_path" ]] || continue
|
[[ -d "$folder_path" ]] || continue
|
||||||
[[ "$(basename "$folder_path")" == .* ]] && continue
|
[[ "$(basename "$folder_path")" == .* ]] && continue
|
||||||
|
if [[ -z "$run_archive_path" ]]; then
|
||||||
|
run_archive_path="$(archive_path_for_run)"
|
||||||
|
log_info "Using single archive for this run: ${run_archive_path}"
|
||||||
|
fi
|
||||||
found=1
|
found=1
|
||||||
process_folder "$folder_path"
|
process_folder "$folder_path" "$run_archive_path"
|
||||||
done
|
done
|
||||||
|
|
||||||
if [[ "$found" -eq 0 ]]; then
|
if [[ "$found" -eq 0 ]]; then
|
||||||
log_warn "No folders found in NFS_SOURCE_PATH=${NFS_SOURCE_PATH}"
|
log_warn "No folders found in NFS_SOURCE_PATH=${NFS_SOURCE_PATH}"
|
||||||
|
elif [[ -f "$run_archive_path" ]]; then
|
||||||
|
local file_size_bytes
|
||||||
|
file_size_bytes="$(wc -c < "$run_archive_path" | tr -d '[:space:]')"
|
||||||
|
if [[ "$file_size_bytes" =~ ^[0-9]+$ ]]; then
|
||||||
|
total_backup_size_bytes="$file_size_bytes"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
print_summary
|
print_summary
|
||||||
@@ -410,6 +470,8 @@ main() {
|
|||||||
die "One or more folder backups failed" 1
|
die "One or more folder backups failed" 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
cleanup_archives || true
|
||||||
|
|
||||||
send_backup_notification \
|
send_backup_notification \
|
||||||
"$NOTIFY_SUCCESS_URL" \
|
"$NOTIFY_SUCCESS_URL" \
|
||||||
"$NOTIFY_TITLE" \
|
"$NOTIFY_TITLE" \
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
NFS_SOURCE_PATH=/path/to/nfs/source
|
NFS_SOURCE_PATH=/path/to/nfs/source
|
||||||
BACKUP_OUTPUT_PATH=/path/to/backup/output
|
BACKUP_OUTPUT_PATH=/path/to/backup/output
|
||||||
BACKUP_PASSWORD=replace-with-strong-password
|
BACKUP_PASSWORD=
|
||||||
KUBECTL_BIN=kubectl
|
KUBECTL_BIN=kubectl
|
||||||
KUBE_CONTEXT=
|
KUBE_CONTEXT=
|
||||||
WORKLOAD_KINDS=deployment,statefulset,replicaset,replicationcontroller
|
WORKLOAD_KINDS=deployment,statefulset,replicaset,replicationcontroller
|
||||||
@@ -19,3 +19,4 @@ NOTIFY_SUCCESS_URL=
|
|||||||
NOTIFY_FAILURE_URL=
|
NOTIFY_FAILURE_URL=
|
||||||
NOTIFY_TITLE=Kubernetes
|
NOTIFY_TITLE=Kubernetes
|
||||||
NOTIFY_ASSET=kube config
|
NOTIFY_ASSET=kube config
|
||||||
|
CLEANUP_DELETE_COUNT=5
|
||||||
|
|||||||
Reference in New Issue
Block a user