Jak to działa:
Serwer TS-451 skonfigurowany z 4 dyskami w trybie pojedynczym. Dysk numer 4 jest zarezerwowany tylko do kopii zapasowej kluczowych danych z dysków 1-3. Kopia jest wyzwalana raz dziennie... Każde dyski są skonfigurowane pojedynczo.
Zalety takiego rozwiązania?
Jeśli myślisz, że RAID to backup i załatwia sprawę to jesteś w błędzie.
Pomyśl... Kupiłeś 4 dyski i zrobiłeś z nich RAID. W pracy każdy dysk był utilizowany tak samo przez ostatnie 2 lata. Który z nich zużył się najbardziej? Wszystkie!
Kiedy jeden z dysków będzie na wykończeniu, bierz pod uwagę, że i zaraz kolejne dyski scenariusz śmierci czeka.
W przypadku konfiguracji pojedynczych dysków zamiast równoległej pracy wszystkich w RAID'zie, tylko dysk numer 1 będzie najczęściej używany. Wszystkie codzienne usługi tj. Torrent, Stacja monitoringu i etc... zostaną skierowane na ten dysk. Pozostałe dyski na, których znajdują się multimedia oraz prywatne dane, będą rzadziej wykorzystywane a tym samym zwiększy się ich żywotność.
Mało tego, na dysk numer 4 zostały zaplanowane dzienne kopie co zdecydowanie zwiększy bezpieczeństwo krytycznych danych!
O zastosowaniu RAID i czym on naprawde jest już pisaliśmy na forum wiele razy...
Rozwiązanie:
1. Pobierz makebackup
2. Edytuj w udziale sieciowym
3. Uruchom mkbackup.sh
4. Jeśli wszystko działa w porządku, dodaj skrypt do crontab'a, np. za pomocą CronWeb:
Source code:
Rezultat
Musze się pochwalić po jednym dniu działania skryptu.
Docelowo ma wyłączać fizycznie dysk po wykonaniu kopii i ponownie go przywrócić... Ale... jeszcze nie jest to do końca zaimplementowane. Ale skrypt śmiało możecie używać jeśli cenicie sobie bezpieczeństwo danych.
Brakuje regułki przywracającej wolumin:
Serwer TS-451 skonfigurowany z 4 dyskami w trybie pojedynczym. Dysk numer 4 jest zarezerwowany tylko do kopii zapasowej kluczowych danych z dysków 1-3. Kopia jest wyzwalana raz dziennie... Każde dyski są skonfigurowane pojedynczo.
Zalety takiego rozwiązania?
Jeśli myślisz, że RAID to backup i załatwia sprawę to jesteś w błędzie.
Pomyśl... Kupiłeś 4 dyski i zrobiłeś z nich RAID. W pracy każdy dysk był utilizowany tak samo przez ostatnie 2 lata. Który z nich zużył się najbardziej? Wszystkie!
Kiedy jeden z dysków będzie na wykończeniu, bierz pod uwagę, że i zaraz kolejne dyski scenariusz śmierci czeka.
W przypadku konfiguracji pojedynczych dysków zamiast równoległej pracy wszystkich w RAID'zie, tylko dysk numer 1 będzie najczęściej używany. Wszystkie codzienne usługi tj. Torrent, Stacja monitoringu i etc... zostaną skierowane na ten dysk. Pozostałe dyski na, których znajdują się multimedia oraz prywatne dane, będą rzadziej wykorzystywane a tym samym zwiększy się ich żywotność.
Mało tego, na dysk numer 4 zostały zaplanowane dzienne kopie co zdecydowanie zwiększy bezpieczeństwo krytycznych danych!
O zastosowaniu RAID i czym on naprawde jest już pisaliśmy na forum wiele razy...
Rozwiązanie:
1. Pobierz makebackup
Bash:
cd /share/Public
wget http://pool.qnapclub.pl/projects/tools/makebackup/makebackup.tar.gz
tar zxvf makebackup.tar.gz && rm makebackup.tar.gz
cd .makebackup
2. Edytuj w udziale sieciowym
Public/.makebackup
pliki backup.conf
oraz backup.lst
w którym określ katalogi źródłowe, które chcesz archiwizować:3. Uruchom mkbackup.sh
Bash:
./mkbackup.sh
4. Jeśli wszystko działa w porządku, dodaj skrypt do crontab'a, np. za pomocą CronWeb:
Kod:
15 5 * * * sh /share/Public/mkbackup/makebackup.sh
Source code:
Bash:
#!/bin/bash
: ${NAME=backup}
##
## Left-To-Right Backup for QNAP
##
## Copyright © 2014, Silas Mariusz <silas@qnap.com.antispam>
##
## QNAP Club Polska
##
##
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation; either version 2, or (at your option)
## any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program; if not, write to the Free Software Foundation,
## Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
##
## The latest version of this software can be obtained here:
##
## https://forum.qnap.net.pl/
##
##
## FILE: backup.sh
##
## RELEASE: 0.4 (beta)
## DATE: 2015-05-03
##
## GENERAL VARIABLES
[[ ! -d "/var/run" ]] && exit 1
LOCK="/var/run/${NAME}.lock"
LOCK_DIR="/var/run/${NAME}"
RETVAL=0 ; _RETVAL=
## Set environment paths ...
_PATHS=\
/bin:\
/sbin:\
/usr/bin:\
/usr/sbin:\
/usr/local/bin:\
/usr/local/sbin:\
/opt/bin:\
/opt/sbin:\
/opt/local/bin:\
/opt/local/sbin:\
:
_LD_PATHS=\
/opt/local/lib:\
/opt/lib:\
:
export PATH=$_PATHS:$PATH:$_PATHS
#export LD_LIBRARY_PATH=$_LD_PATHS:$LD_LIBRARY_PATH:$_LD_PATHS
## Ansi colors
## -----------------------------------------------------------------
## Normal Colors Bold Colors # Color
## ---------------------------------------------#-------------------
black='\e[0;30m' ; bblack='\e[1;30m' ; # Black
red='\e[0;31m' ; bred='\e[1;31m' ; # Red
green='\e[0;32m' ; bgreen='\e[1;32m' ; # Green
yellow='\e[0;33m' ; byellow='\e[1;33m' ; # Yellow
blue='\e[0;34m' ; bblue='\e[1;34m' ; # Blue
purple='\e[0;35m' ; bpurple='\e[1;35m' ; # Purple
cyan='\e[0;36m' ; bcyan='\e[1;36m' ; # Cyan
white='\e[0;37m' ; bwhite='\e[1;37m' ; # White
## ---------------------------------------------#-------------------
## Background
on_black='\e[40m' ; # On Black
on_red='\e[41m' ; # On Red
on_green='\e[42m' ; # On Green
on_yellow='\e[43m' ; # On Yellow
on_blue='\e[44m' ; # On Blue
on_purple='\e[45m' ; # On Purple
on_cyan='\e[46m' ; # On Cyan
on_white='\e[47m' ; # On White
## ---------------------------------------------#-------------------
## Colors Presets
alert="${bwhite}${on_red}" ; i_alert="${bred}${on_white}" #alert
note="${bwhite}${on_blue}" ; i_note="${bblue}${on_white}" #note
info="$note" ; i_info="$i_note" #info
## ---------------------------------------------#-------------------
## Reset colour/restore default
nc="\e[m"
## ---------------------------------------------#--------[ DONE ]---
## Environment
fd=0 # stdin
export HOME=/root
export TERM=xterm
export USER=admin
export PWD=/root
export EDITOR=/bin/vi
export LOGNAME=admin
: ${AWK=awk}
## interactive shell? then keep user attention on the err msg
## -----------------------------------------------------------------
if [ -t "$fd" ] || [ -p /dev/stdin ]; then
echo -n "]0; logman-[QNAP] "
fi
## Signals
SIGNAL_HUP=1 ; SIGNAL_INT=2 ; SIGNAL_QUIT=3 ; SIGNAL_ILL=4
SIGNAL_TRAP=5 ; SIGNAL_ABRT=6 ; SIGNAL_BUS=7 ; SIGNAL_FPE=8
SIGNAL_KILL=9 ; SIGNAL_USR1=10 ; SIGNAL_SEGV=11 ; SIGNAL_USR2=12
SIGNAL_PIPE=13 ; SIGNAL_ALRM=14 ; SIGNAL_TERM=15 ; SIGNAL_STKFLT=16
SIGNAL_CHLD=17 ; SIGNAL_CONT=18 ; SIGNAL_STOP=19 ; SIGNAL_TSTP=20
SIGNAL_TTIN=21 ; SIGNAL_TTOU=22 ; SIGNAL_URG=23 ; SIGNAL_XCPU=24
SIGNAL_XFSZ=25 ; SIGNAL_VTALRM=26 ; SIGNAL_PROF=27 ; SIGNAL_WINCH=28
SIGNAL_IO=29 ; SIGNAL_PWR=30 ; SIGNAL_SYS=31 ; #End
# uint signal (uint <pid>, int <signal number>|string <signal name>)
function signal {
local -i PID=$1 SIGNAL=$2 ## Automatically expands if $1 is a variable.
kill -s "$SIGNAL" "$PID" 2>&"$NULL"
}
# debug exec - (if _DEBUG is non zero)
function DEBUG()
{
[[ "$_DEBUG" == "0" ]] || $@
}
function _exit()
{
echo -e "$*"
echo
# interactive shell? then keep user attention on the err msg
if [[ -t "$fd" ]] || [[ -p /dev/stdin ]]; then
echo -n "]0; ! ERROR - - - - - " ; sleep 1
echo -n "]0; - - - - - ERROR ! " ; sleep 1
echo -n "]0; - - - - - ERROR! " ; sleep 1
echo -n "]0; - - - - - ERROR ! " ; sleep 1
echo -n "]0; !ERROR - - - - - " ; sleep 1
fi
exit 1
}
#alias strip_esc='sed -r "s/\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[m|K|A|B|C]//g"'
alias strip_esc='sed -r "s/\x1B[\[|\(]([0-9]{1,2}(;[0-9]{1,2})?)?[m|K|A|B|C|E|J|S|Z|H]//g"'
shopt -s expand_aliases
# Message to terminal and system log
# ###########################################################################
_log() {
local msg_type="info"
local write_msg="/sbin/log_tool -t0 -u$NAME -p127.0.0.1 -mlocalhost -a"
local message="$NAME: ${*:-"Unspecified Notice"}"
[ "$_QUIET" != "1" ] && echo -e "(${green}$msg_type${nc}) $message"
# save to system Event Log only when in DEBUG mode
[[ "$_DEBUG" == "1" ]] && $write_msg "($msg_type) $(echo $message | strip_esc)"
}
# Warning message to terminal and system log
# ###########################################################################
_warn() {
local msg_type="warn"
local write_warn="/sbin/log_tool -t1 -u$NAME -p127.0.0.1 -mlocalhost -a"
local message="$NAME: ${*:-"Unknown Warning"}" && $write_warn "($msg_type) $(echo $message | strip_esc)"
echo -e "(${red}$msg_type${nc}) $message"
}
# Write error log message and exit
# ###########################################################################
_err(){
local msg_type="err!"
local write_err="/sbin/log_tool -t2 -u$NAME -p127.0.0.1 -mlocalhost -a"
local message="$NAME: ${*:-"Unknown Error"}" && $write_err "($msg_type) $(echo $message | strip_esc)"
_exit "(${alert}$msg_type${nc}) $message \n"
}
available() {
type -t "$1" >/dev/null && return 0
return 1
}
need() {
available "$1" && return 0
_err needed command was not found: $1
exit 1
}
alias finddev='getcfg "PhysicalDisk_$DISKNO" pd_sys_name -f /etc/enclosure_0.conf -d error | cut -d \/ -f 3'
# Let external disk go sleep...
drive_readd() {
_device=$($finddev)
if [[ "$_device" == "error" ]] || [[ ! -x "/dev/$_device" ]]; then
echo "Hardware rescaning... (About 2 minutes)"
scsihosts=$(ls -1 "/sys/class/scsi_host/" | grep host | tr -d "@" | tac)
for i in $scsihosts ; do
_sd_dev=$(ls -1 /sys/block/ | grep sd | tr -d "/") ; echo Found: $_sd_dev
echo "0 0 0" >"/sys/class/scsi_host/$i/scan" 2>/dev/null
_device=$($finddev)
[[ "$_device" != "error" ]] && [[ -e "/dev/$_device" ]] && break
done
sleep 10
sync
_device=$($finddev)
[[ "$_device" == "error" ]] && _err "Physical disk $DISKNO not found in /etc/enclosure_0.conf"
else
_log "HDD $DISKNO: Device: /dev/${green}$_device${nc} Target: ${yellow}$RIGHT${nc}/$POOL"
fi
}
# Let external disk go sleep...
drive_remove() {
_device=$($finddev)
[[ "$_device" == "error" ]] && return 1
sync
sleep 5
hdparm -S 1 /dev/$_device
hdparm -y /dev/$_device
umount /dev/$_device
echo offline > /sys/block/$_device/device/state
echo 1 > /sys/block/$_device/device/delete
return 0
}
need ipkg
ipkg update 2>&1 >/dev/null
ipkg install findutils 2>&1 >/dev/null
ipkg install rsync 2>&1 >/dev/null
ipkg install binutils 2>&1 >/dev/null
ipkg install util-linux-ng 2>&1 >/dev/null
need rsync
need ionice
need find
DATE=`date +%Y-%m-%d` ; TIME=`date +%H:%M:%S` ; NOW=`date +%Y%m%d_%H%M%S`
# ###########################################################################
# Basics
# ###########################################################################
# find my name and real current directory
# ###########################################################################
PROGNAME=`type $0 | awk '{print $3}'` # search for executable on path
PROGDIR=`dirname $PROGNAME` # extract directory of program
PROGNAME=`basename $PROGNAME` # base name of program
[ "$PROGDIR" == "." ] && PROGDIR=`cd $PROGDIR && pwd || echo $PROGDIR`
Int_dPWD=`cd $PROGDIR && pwd || echo $PROGDIR`
if [ "${Int_dPWD}" = "/etc/init.d" ] || [ "${Int_dPWD}" = "/etc/rcS.d" ] || [ "${Int_dPWD}" = "/etc/rcK.d" ]; then
[ -L "${PROGDIR}/${PROGNAME}" ] && {
_SLINK=$(readlink ${PROGDIR}/${PROGNAME})
PROGDIR=`dirname ${_SLINK}`
PROGNAME=`basename ${_SLINK}`
}
fi
# Fully qualify directory path (remove relative components and symlinks)
# WARNING: The "bash" "pwd" builtin does not resolve symbolic links
# "sh" (as a link to "bash") also does not, but "csh" does handle it.
# Symbolic link removal however is not critical.
ORIGDIR=`pwd` # original directory (builtin)
PROGDIR=`cd $PROGDIR && pwd || echo $PROGDIR` # program directory
# Results...
# $ORIGDIR -- where the users was when called
# $PROGDIR -- script directory location (and now current directory)
# $PROGNAME -- This scripts executable name
# alternatively use this...
# Get the fully qualified path to the script
# ###########################################################################
case $0 in
/*)
SCRIPT="$0"
;;
*)
PWD=`pwd`
SCRIPT="$PWD/$0"
;;
esac
# Change spaces to ":" so the tokens can be parsed.
SCRIPT=`echo $SCRIPT | sed -e 's; ;:;g'`
# Get the real path to this script, resolving any symbolic links
TOKENS=`echo $SCRIPT | sed -e 's;/; ;g'`
REALPATH=
for C in $TOKENS; do
REALPATH="$REALPATH/$C"
while [ -h "$REALPATH" ] ; do
LS="`ls -ld "$REALPATH"`"
LINK="`expr "$LS" : '.*-> \(.*\)$'`"
if expr "$LINK" : '/.*' > /dev/null; then
REALPATH="$LINK"
else
REALPATH="`dirname "$REALPATH"`""/$LINK"
fi
done
done
# Change ":" chars back to spaces.
REALPATH=`echo $REALPATH | sed -e 's;:; ;g'`
REALPATH="`dirname "$REALPATH"`"
[ "`basename $REALPATH`" == "." ] && \
REALPATH="`echo $REALPATH | awk '{$0=substr($0,1,length($0)-2); print $0}'`"
# Change the current directory to the location of the script
PROGDIR="`cd $REALPATH && pwd || echo $PROGDIR`"
cd "$PROGDIR"
## LOG PATH
MKBAKCONF="/share/Public/.makebackup"
[[ -d "$MKBAKCONF" ]] || mkdir "$MKBAKCONF"
LOGDIR="$MKBAKCONF"
LOGFILE="$LOGDIR/rsync_`date +%Y-%m-%d`.log"
SOURCES="$MKBAKCONF/backup.lst"
if [ ! -f "$SOURCES" ]; then
echo -e "/share/Public/directory1\n/share/Multimedia/Family\n/etc\n..." > $SOURCES.example
_log "Create list file with source directories to backup: ${white}$SOURCES${nc}"
_err "Backup list not found $SOURCES"
exit 1
fi
LEFT=()
s=0
while read line; do
#LEFT[s]="$(echo -e "$line" | sed 's/ /\\ /g')"
LEFT[s]="$(echo -e "$line")"
s=$(expr $s + 1)
done < "$SOURCES"
echo -e "${bwhite}Source list${nc}: \n${LEFT[@]} \n"
CONFIG="$MKBAKCONF/backup.conf"
[ -f "$CONFIG" ] || _err "Config file not found $CONFIG"
RIGHT=$(getcfg Backup RIGHT -f "$CONFIG" -d error)
echo -e "${bwhite}Remote path${nc}: \n$RIGHT \n"
DISKNO=$(getcfg Backup DISKNO -f "$CONFIG" -d error)
SPINDRIVE=$(getcfg Backup SPINDRIVE -f "$CONFIG" -d 0)
IGNORE="$MKBAKCONF/ignore.lst"
[[ -f "$IGNORE" ]] || _err "Ignore list file not found $IGNORE"
[[ -d "$RIGHT" ]] || mkdir "$RIGHT"
[[ -d "$RIGHT" ]] || _err "Destination directory path is wrong..."
# ###########################################################################
# Global Variables
# ###########################################################################
# Time Measure Command
STA_NOWTIME="date +%s"
## func: Elapsed Time (calculate diff)
## ###########################################################################
elapsedTime() {
local _startTime="$1"
local _endTime="$2"
echo "$(($_endTime - $_startTime))"
}
#-----------------------------------------------------------------------
# Private Signal Traps Functions {{{2
#
# DANGER: SIGKILL cannot be trapped. So, try not to `kill -9 PID` or
# there will be *NO CLEAN UP*. You'll have to manually remove
# any locks in place.
#-----------------------------------------------------------------------
function __make_locks {
if [ ! -e "${LOCK}" ] ; then
touch "${LOCK}"
else
_err application already started... Lock found ${LOCK}
fi
}
function __clean_locks {
[ -f "$LOCK" ] && rm "$LOCK"
}
function __sig_exit {
__clean_locks
}
function __sig_int {
echo -e "\n${warn} WARNING: ${nc} ${bred}SIGINT ${nc}${red}caught${nc}"
__clean_locks
exit 1002
}
function __sig_quit {
echo -e "\n${bpurple} SIGQUIT ${nc}${purple}caught${nc}"
__clean_locks
exit 1003
}
function __sig_term {
echo -e "\n${warn} WARNING: ${nc} ${bred}SIGTERM ${nc}${red}caught${nc}"
__clean_locks
exit 1015
}
# Set TRAPs
trap __sig_exit EXIT # SIGEXIT
#trap __sig_int INT # SIGINT
trap __sig_quit QUIT # SIGQUIT
trap __sig_term TERM # SIGTERM
# Create lock file to prevent double run
__make_locks
# Readd/remount disk drive
[[ "$SPINDRIVE" == "1" ]] && drive_readd
# start time
_startMeasureTime=$($STA_NOWTIME)
cd "$RIGHT" && \
$PROGDIR/sayebackup.sh \
--inc \
--relative \
-C "$RIGHT/" \
-E "$IGNORE" \
-o bak \
"${LEFT[@]}" \
| tee -a "$LOGFILE"
_RETVAL=${PIPESTATUS[0]}
if [ $_RETVAL -eq 0 ]; then
_log "Sync done: -> $RIGHT"
else
[ "$_QUIET" != "1" ] && _warn "error occured while rsync: -> $RIGHT"
RETVAL=1
fi
# end time
_endMeasureTime=$($STA_NOWTIME)
_diffMeasure=$(elapsedTime $_startMeasureTime $_endMeasureTime)
_log "Backup job done in $_diffMeasure seconds."
__clean_locks
# Force remove drive from OS/spin down
if [ "$SPINDRIVE" == "1" ]; then
drive_remove
_RETVAL=$?
if [ $_RETVAL -eq 0 ]; then
_log "Disk disabled"
else
_warn "Failed while trying to disable disk drive"
fi
fi
[ $RETVAL -ne 0 ] && _err "Errors occured."
exit $RETVAL
# Parent process has died so we also better die.
exit 0
Rezultat
Musze się pochwalić po jednym dniu działania skryptu.
Docelowo ma wyłączać fizycznie dysk po wykonaniu kopii i ponownie go przywrócić... Ale... jeszcze nie jest to do końca zaimplementowane. Ale skrypt śmiało możecie używać jeśli cenicie sobie bezpieczeństwo danych.
Brakuje regułki przywracającej wolumin:
Bash:
[~] # df
Filesystem Size Used Available Use% Mounted on
none 200.0M 148.0M 52.0M 74% /
devtmpfs 1.9G 4.0k 1.9G 0% /dev
tmpfs 64.0M 3.6M 60.4M 6% /tmp
tmpfs 1.9G 4.0k 1.9G 0% /dev/shm
/dev/md9 509.5M 108.2M 401.3M 21% /mnt/HDA_ROOT
/dev/mapper/cachedev1 6.8T 28.2G 6.7T 0% /share/CACHEDEV1_DATA
/dev/md13 371.0M 282.3M 88.7M 76% /mnt/ext
/dev/mapper/cachedev2 909.0G 76.0M 908.5G 0% /share/CACHEDEV2_DATA
[~] # umount /dev/mapper/cachedev2
[~] # dmsetup remove cachedev2
[~] # vgchange -an vg2
0 logical volume(s) in volume group "vg2" now active
[~] # lvs
LV VG Attr LSize Pool Origin Data% Move Log Copy% Convert
lv1 vg1 Vwi-aot- 6.80t tp1 100.00
lv544 vg1 -wi----- 20.00g
tp1 vg1 twi-a-t- 7.22t 94.13
lv2 vg2 -wi----- 912.80g
lv545 vg2 -wi----- 9.22g
[~] # mdadm -S /dev/md2
mdadm: stopped /dev/md2
[~] #
[~] # echo 1 > /sys/block/sde/device/delete
Bash:
[~] # echo "0 0 0" > /sys/class/scsi_host/host3/scan
[~] #
[~] # storage_util --sys_startup_p2
sys_startup_p2:got called count = 3
Perform NAS model checking...
NAS model match, skip model migration.
[~] # df
Filesystem Size Used Available Use% Mounted on
none 200.0M 148.1M 51.9M 74% /
devtmpfs 1.9G 4.0k 1.9G 0% /dev
tmpfs 64.0M 3.6M 60.4M 6% /tmp
tmpfs 1.9G 4.0k 1.9G 0% /dev/shm
/dev/md9 509.5M 108.2M 401.3M 21% /mnt/HDA_ROOT
/dev/mapper/cachedev1 6.8T 28.2G 6.7T 0% /share/CACHEDEV1_DATA
/dev/md13 371.0M 282.3M 88.7M 76% /mnt/ext
/dev/mapper/cachedev2 909.0G 76.0M 908.5G 0% /share/CACHEDEV2_DATA