db_backup.sh 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  1. #!/usr/bin/env bash
  2. set -e
  3. set -o pipefail
  4. if [ "$DEBUG" == "true" ]; then
  5. set -ex
  6. export PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }'
  7. fi
  8. export PATH=$PATH:/opt/yunion/bin
  9. export KUBECONFIG=/etc/kubernetes/admin.conf
  10. export BKUP_DATE=$(date +"%Y%m%d-%H%M%S")
  11. export BKUP_PATH=${BKUP_PATH:-/opt/yunion/backup}/$BKUP_DATE
  12. export DB_HOST=
  13. export DB_USER=
  14. export DB_PSWD=
  15. export DB_PORT=
  16. export MYSQL_BACKUP_ARGS='--add-drop-database --add-drop-table --add-locks --single-transaction --quick'
  17. export MAX_BKUP=${MAX_BKUP:-10}
  18. export LIGHT_BKUP=${LIGHT_BKUP:-true}
  19. export VERBOSE=${VERBOSE:-false}
  20. export PV_ARGS='pv --wait --timer --rate --eta --size '
  21. export MAX_DISK_PERCENTAGE=${MAX_DISK_PERCENTAGE:-75}
  22. export K3S_CMDLINE_PREFIX=
  23. if hash k3s && [[ -s ~/.kube/config ]] &>/dev/null; then
  24. export KUBECONFIG=~/.kube/config
  25. export K3S_CMDLINE_PREFIX=k3s
  26. fi
  27. dbs=()
  28. __info() {
  29. line="$*"
  30. local regex="mysqldump|mysql|hostname|Light|DBs|Size:.*B\b|Backup result|Backup Disk"
  31. echo "[$(date +"%F %T")] $line" | grep --no-messages --color=auto --ignore-case --perl-regexp "$regex" ||
  32. echo "[$(date +"%F %T")] $line"
  33. }
  34. convert_seconds() {
  35. local seconds="$1"
  36. local hours=$((seconds / 3600))
  37. local minutes=$(((seconds % 3600) / 60))
  38. local seconds=$((seconds % 60))
  39. if [ "$hours" -gt 0 ]; then
  40. echo "$hours hours, $minutes minutes, $seconds seconds"
  41. elif [ "$minutes" -gt 0 ]; then
  42. echo "$minutes minutes, $seconds seconds"
  43. else
  44. echo "$seconds seconds"
  45. fi
  46. }
  47. title() {
  48. local termwidth="$(tput cols)"
  49. termwidth=$((termwidth / 2))
  50. local padding="$(printf '%0.1s' ={1..500})"
  51. printf '\n%*.*s %s %*.*s\n' 0 "$(((termwidth - 2 - ${#1}) / 2))" "$padding" "$1" 0 "$(((termwidth - 1 - ${#1}) / 2))" "$padding"
  52. }
  53. check_bin_exist() {
  54. for bin in "$@"; do
  55. __info $($bin --version)
  56. done
  57. }
  58. check_backup_disk() {
  59. local disk
  60. if [ ! -d "$BKUP_PATH" ]; then
  61. mkdir -p "$BKUP_PATH"
  62. fi
  63. disk=$(df --output=source "$BKUP_PATH" | grep -v '^Filesystem' | grep -v '文件系统' | head -1)
  64. if [ -z "$disk" ]; then
  65. "get disk error!"
  66. exit 1
  67. fi
  68. disk_usage=$(df -h --output=pcent --exclude-type=tmpfs,devtmpfs "$disk" | grep -v 'Use%' | head -1 | tr -d % | xargs)
  69. if [ "$disk_usage" -gt $MAX_DISK_PERCENTAGE ]; then
  70. __info "$BKUP_PATH is on disk $disk, usage:$disk_usage%, too high to continue backup."
  71. __info "Allowed maximum disk usage: $MAX_DISK_PERCENTAGE."
  72. # only delete backup path when it is empty and ends with timestamp like 20240506-163855
  73. if [ -z "$(ls -A $BKUP_PATH)" ] && [[ "$BKUP_PATH" =~ [0-9]{8}-[0-9]{6}$ ]]; then
  74. rm -rf "$BKUP_PATH"
  75. fi
  76. exit 1
  77. fi
  78. __info "Backup Disk: $disk; Used: $disk_usage%"
  79. }
  80. init_db() {
  81. local
  82. title "Init"
  83. check_backup_disk
  84. local content="$($K3S_CMDLINE_PREFIX kubectl get onecloudcluster -n onecloud default -o yaml | grep -A 5 -w mysql)"
  85. export DB_HOST=$(echo "$content" | grep -w host: | awk '{print $(NF)}')
  86. export DB_USER=$(echo "$content" | grep -w username: | awk '{print $(NF)}')
  87. export DB_PSWD=$(echo "$content" | grep -w password: | awk '{print $(NF)}')
  88. export DB_PORT=$(echo "$content" | grep -w port: | awk '{print $(NF)}')
  89. export DB_PORT=${DB_PORT:-3306}
  90. local size
  91. if ! check_bin_exist mysql mysqldump; then
  92. __info "mysql or mysqldump is missing!"
  93. exit 1
  94. fi
  95. if [ -n "$DB_HOST" ] && [ -n "$DB_USER" ] && [ -n "$DB_PSWD" ]; then
  96. dbs=($(get_dbs))
  97. size=$(get_all_db_size_bytes)
  98. size=$(numfmt --to=iec --suffix=B "$size")
  99. else
  100. __info "init db error"
  101. exit 1
  102. fi
  103. local flag=Full
  104. if [[ "$LIGHT_BKUP" == "true" ]]; then
  105. flag=Light
  106. fi
  107. __info "Hostname: $(hostname), IP: $(ip route get 1 | head -1 | awk '{print $7}')"
  108. __info "performing $flag backup."
  109. __info "DBs to backup: [${dbs[*]}]"
  110. __info "Size: $size before compress."
  111. }
  112. query() {
  113. mysql --skip-column-names -h "$DB_HOST" -u "$DB_USER" -p"$DB_PSWD" -P "$DB_PORT" "$@"
  114. }
  115. get_dbs() {
  116. local filter
  117. if [ "$LIGHT_BKUP" == true ]; then
  118. filter='s#^\(Database\|mysql\|performance_schema\|test\|information_schema\|yunionlogger\|yunionmeter\)$##g'
  119. else
  120. filter='s#^\(Database\|mysql\|performance_schema\|test\|information_schema\)$##g'
  121. fi
  122. query -e 'show databases;' | sed -e "$filter" | sort
  123. }
  124. # --ignore-table=DB.table
  125. get_ignore_tables_for_db() {
  126. local db="$1"
  127. local sql
  128. local tables=()
  129. if [ -z "$db" ]; then
  130. return
  131. fi
  132. sql="show tables from \`$db\` where \`Tables_in_$db\` like 'opslog%' or \`Tables_in_$db\` like 'task%';"
  133. tables=($(query -e "$sql"))
  134. for table in "${tables[@]}"; do
  135. echo "--ignore-table=$db.$table"
  136. done
  137. }
  138. get_all_ignored_tables() {
  139. local tables=()
  140. for db in "${dbs[@]}"; do
  141. tables+=($(get_ignore_tables_for_db $db))
  142. done
  143. echo "${tables[@]}"
  144. }
  145. backup_db() {
  146. local timestamp=$BKUP_DATE
  147. local args=()
  148. local output="$BKUP_PATH/onecloud.sql.$timestamp.gz"
  149. local startTime
  150. local endTime
  151. startTime=$(date +"%s")
  152. title "Backup Database"
  153. args=(${MYSQL_BACKUP_ARGS[@]} -h $DB_HOST -u $DB_USER -p"$DB_PSWD" -P "$DB_PORT")
  154. args+=(--databases ${dbs[@]})
  155. if [ "$LIGHT_BKUP" == true ]; then
  156. args+=($(get_all_ignored_tables))
  157. fi
  158. if [ -x /usr/bin/pv ]; then
  159. mysqldump "${args[@]}" | ${PV_ARGS[@]} $(get_all_db_size_bytes) | gzip -c >"$output"
  160. else
  161. mysqldump "${args[@]}" | gzip -c >"$output"
  162. fi
  163. endTime=$(date +"%s")
  164. __info "It takes $(convert_seconds $((endTime - startTime)))."
  165. }
  166. get_db_size_bytes() {
  167. local db="$1"
  168. local sql
  169. if [ -z "$db" ]; then
  170. return
  171. fi
  172. sql='SELECT
  173. sum(ROUND((DATA_LENGTH + INDEX_LENGTH )*0.85) )
  174. FROM information_schema.TABLES
  175. WHERE TABLE_SCHEMA = "'$db'" '
  176. if [[ "$LIGHT_BKUP" == "true" ]]; then
  177. sql="$sql"'AND table_name not like "opslog%"
  178. AND table_name not like "task%"'
  179. fi
  180. query -e "$sql" | xargs
  181. }
  182. get_all_db_size_bytes() {
  183. local sum=0
  184. local db
  185. local size
  186. for db in "${dbs[@]}"; do
  187. size=$(get_db_size_bytes $db)
  188. sum=$((sum + size))
  189. done
  190. echo $sum
  191. }
  192. rotate_bkups() {
  193. local old_bkups=()
  194. local max=$((MAX_BKUP + 1))
  195. (
  196. cd "$(dirname $BKUP_PATH)"
  197. if ! [ -d "$BKUP_PATH" ]; then
  198. __info "backup path $BKUP_PATH dose not exist!"
  199. exit 1
  200. fi
  201. __info "Archive Backup Files..."
  202. find $BKUP_PATH -type f | xargs ls -lah
  203. tar czvf onecloud.bkup.$BKUP_DATE.tar.gz $BKUP_DATE && rm -rf $BKUP_DATE
  204. echo
  205. __info "Backup result: "
  206. ls -lah $(readlink -f onecloud.bkup.$BKUP_DATE.tar.gz)
  207. )
  208. old_bkups=($(find $(dirname $BKUP_PATH) -name "onecloud*.gz" | sort --reverse | tail -n +$max))
  209. title "Cleaning"
  210. if [ "${#old_bkups[@]}" -gt 0 ]; then
  211. __info "cleaning old backups: ${old_bkups[*]}"
  212. if ! rm -f "${old_bkups[@]}"; then
  213. __info "rm old backup files error! ${old_bkups[*]}"
  214. exit 1
  215. fi
  216. fi
  217. title "All DONE"
  218. }
  219. backup_etcd() {
  220. title "Backup ETCD"
  221. local etcd_id="$(docker ps -a | grep etcd | grep Up | grep -v pause | grep 'etcd --adv' | head -1 | awk '{print $1}' || :)"
  222. local etcdctl=(docker exec $etcd_id /usr/local/bin/etcdctl)
  223. if [ -z "$etcd_id" ]; then
  224. echo "Docekr or ETCD is not running. ignore etcd backup"
  225. return
  226. fi
  227. __info "$(${etcdctl[@]} version | xargs)"
  228. ETCDCTL_API=3 ${etcdctl[@]} snapshot save /tmp/etcd_snapshot_$BKUP_DATE.db \
  229. --cacert /etc/kubernetes/pki/etcd/ca.crt \
  230. --cert /etc/kubernetes/pki/etcd/server.crt \
  231. --key /etc/kubernetes/pki/etcd/server.key
  232. ETCDCTL_API=3 ${etcdctl[@]} snapshot status /tmp/etcd_snapshot_$BKUP_DATE.db
  233. docker cp $etcd_id:/tmp/etcd_snapshot_$BKUP_DATE.db $BKUP_PATH
  234. docker exec $etcd_id rm -f /tmp/etcd_snapshot_$BKUP_DATE.db
  235. }
  236. backup_k8s() {
  237. title "Backup K8s"
  238. $K3S_CMDLINE_PREFIX kubectl -n onecloud -o yaml get deployment onecloud-operator >$BKUP_PATH/onecloud-operator.$BKUP_DATE.yml
  239. $K3S_CMDLINE_PREFIX kubectl -n onecloud -o yaml get oc >$BKUP_PATH/oc.$BKUP_DATE.yml
  240. sed -i -e '/^ *creationTimestamp:/d' -e '/^ *resourceVersion:/d' -e '/^ *selfLink:/d' -e '/^ *uid:/d' $BKUP_PATH/onecloud-operator.$BKUP_DATE.yml
  241. sed -i -e '/^ *creationTimestamp:/d' -e '/^ *resourceVersion:/d' -e '/^ *selfLink:/d' -e '/^ *uid:/d' $BKUP_PATH/oc.$BKUP_DATE.yml
  242. __info "K8s backup files:"
  243. find $BKUP_PATH -name '*yml' -type f | xargs ls -lah
  244. }
  245. main() {
  246. init_db
  247. backup_k8s
  248. backup_etcd
  249. backup_db
  250. rotate_bkups
  251. }
  252. main