Skip to content

Commit 912e251

Browse files
author
lead4good
committed
init
0 parents  commit 912e251

File tree

4 files changed

+325
-0
lines changed

4 files changed

+325
-0
lines changed

Dockerfile

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
FROM debian:jessie
2+
3+
RUN \
4+
echo "deb http://repo.percona.com/apt jessie main" > /etc/apt/sources.list.d/percona-release.list \
5+
&& apt-key adv --keyserver ha.pool.sks-keyservers.net --recv-keys 9334A25F8507EFA5 \
6+
&& apt-get update \
7+
&& apt-get install --no-install-recommends --no-install-suggests -y \
8+
percona-xtrabackup-24 percona-toolkit qpress bash \
9+
&& mkdir -p /backups && mkdir -p /var/lib/mysql \
10+
&& apt-get -y autoremove && apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
11+
12+
# Allow mountable backup path
13+
VOLUME ["/backups"]
14+
15+
# Copy the script to simplify backup command
16+
COPY backup.sh /backup.sh
17+
COPY xtrabackup.sh /xtrabackup.sh

README.md

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# Supported tags and respective `Dockerfile` links
2+
3+
- [`latest` (*xtrabackup/Dockerfile*)](https://github.com/gleez/docker-images/blob/master/xtrabackup/Dockerfile)
4+
5+
# Percona Xtrabackup
6+
7+
Derived from the official Docker Debian Jessie image. The image contains Percona Xtrabackup installed and a simple bash script to run the backup command.
8+
9+
# How to use this image?
10+
11+
To run the backup, link it to the running MySQL container and ensure to map the following volumes correctly:
12+
13+
- MySQL datadir of the running MySQL container: /var/lib/mysql
14+
- Backup destination: /backups
15+
16+
## Example
17+
18+
Suppose you have a MySQL container running named "mysql-server", started with this command:
19+
20+
```bash
21+
$ docker run -d \
22+
--name=mysql-server \
23+
-v /storage/mysql-server/datadir:/var/lib/mysql \
24+
-e MySQL_ROOT_PASSWORD=mypassword \
25+
mysql
26+
```
27+
28+
Then, to perform backup against the above container, the command would be:
29+
30+
```bash
31+
$ docker run -it \
32+
-v /storage/mysql-server/datadir:/var/lib/mysql \
33+
-v /storage/backups:/backups \
34+
--rm=true \
35+
gleez/xtrabackup \
36+
sh -c 'exec /xtrabackup.sh'
37+
```
38+
39+
You should see Xtrabackup output on the screen. Ensure you get the “completed OK” line indicating the backup is successful:
40+
41+
```bash
42+
...
43+
innobackupex: Backup created in directory '/backups/2017-02-02_07-00-28'
44+
170202 17:07:57 innobackupex: Connection to database server closed
45+
170202 17:07:57 innobackupex: completed OK!
46+
```
47+
48+
The container will then exit (the "run" is executed interactively) and automatically removed by Docker since we specified “--rm=true” in the command line. On the machine host, we can see the backups are there:
49+
50+
```bash
51+
$ ls -1 /storage/backups/
52+
2017-02-02_07-00-28
53+
2017-01-17_13-07-28
54+
2017-01-17_14-02-50
55+
```
56+

backup.sh

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Run innobackupex
2+
BACKUP_PATH=/backups
3+
4+
innobackupex --host="$MYSQL_PORT_3306_TCP_ADDR" \
5+
--port="$MYSQL_PORT_3306_TCP_PORT" \
6+
--user=root \
7+
--password="$MYSQL_ENV_MYSQL_ROOT_PASSWORD" \
8+
$BACKUP_PATH

xtrabackup.sh

Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
#!/bin/bash
2+
3+
die () {
4+
echo -e 1>&2 "$@"
5+
exit 1
6+
}
7+
8+
fail () {
9+
die "...FAILED! See $LOG_FILE for details - aborting.\n"
10+
}
11+
12+
INNOBACKUPEX=$(which innobackupex)
13+
[ -f "$INNOBACKUPEX" ] || die "innobackupex script not found - please ensure xtrabackup is installed before proceeding."
14+
15+
16+
##CONTAINER=$(export | sed -nr "/ENV_MYSQL_DATABASE/{s/^.+ -x (.+)_ENV.+/\1/p;q}")
17+
##DB_PORT=$(export | sed -nr "/-x ${CONTAINER}_PORT_[[:digit:]]+_TCP_PORT/{s/^.+ -x (.+)=.+/\1/p}")
18+
##DB_ADDR="${CONTAINER}_PORT_${!DB_PORT}_TCP_ADDR"
19+
##DB_NAME="${CONTAINER}_ENV_MYSQL_DATABASE"
20+
##DB_PASS="${CONTAINER}_ENV_MYSQL_ROOT_PASSWORD"
21+
22+
CONFIG_FILE=/backups/.xtrabackup.config
23+
24+
if [ -f $CONFIG_FILE ]; then
25+
echo -e "Loading configuration from $CONFIG_FILE."
26+
source $CONFIG_FILE
27+
else
28+
cat << EOF > $CONFIG_FILE
29+
MYSQL_HOST=mysql-host
30+
MYSQL_PORT=3306
31+
MYSQL_USER="$(whoami)"
32+
MYSQL_PASS=
33+
MYSQL_DATA_DIR=/var/lib/mysql/
34+
BACKUPS_DIRECTORY=/backups/mysql-backups
35+
MAX_BACKUP_CHAINS=8
36+
EOF
37+
38+
die "Configuration has been initialised in $CONFIG_FILE. \nPlease make sure all settings are correctly defined/customised - aborting."
39+
fi
40+
41+
[ -d $MYSQL_DATA_DIR ] || die "Please ensure the MYSQL_DATA_DIR setting in the configuration file points to the directory containing the MySQL databases."
42+
[ -n "$MYSQL_USER" -a -n "$MYSQL_PASS" ] || die "Please ensure MySQL username and password are properly set in the configuration file."
43+
44+
FULLS_DIRECTORY=$BACKUPS_DIRECTORY/full
45+
INCREMENTALS_DIRECTORY=$BACKUPS_DIRECTORY/incr
46+
LOGS="/backups/logs"
47+
48+
49+
mkdir -vp $FULLS_DIRECTORY
50+
mkdir -vp $INCREMENTALS_DIRECTORY
51+
mkdir -vp $LOGS
52+
53+
IONICE=$(which ionice)
54+
55+
if [ -n "$IONICE" ]; then
56+
IONICE_COMMAND="$IONICE -c2 -n7"
57+
fi
58+
59+
INNOBACKUPEX_COMMAND="$(which nice) -n 15 $IONICE_COMMAND $INNOBACKUPEX"
60+
RSYNC_COMMAND="$(which nice) -n 15 $IONICE_COMMAND $(which rsync)"
61+
62+
full_backup () {
63+
$INNOBACKUPEX_COMMAND --slave-info --host="$MYSQL_HOST" --port=$MYSQL_PORT --user="$MYSQL_USER" --password="$MYSQL_PASS" "$FULLS_DIRECTORY"
64+
65+
NEW_BACKUP_DIR=$(find $FULLS_DIRECTORY -mindepth 1 -maxdepth 1 -type d -exec ls -dt {} \+ | head -1)
66+
67+
echo $NEW_BACKUP_DIR > $NEW_BACKUP_DIR/backup.chain
68+
}
69+
70+
incremental_backup () {
71+
LAST_BACKUP=${LAST_CHECKPOINTS%/xtrabackup_checkpoints}
72+
73+
$INNOBACKUPEX_COMMAND --slave-info --host="$MYSQL_HOST" --port=$MYSQL_PORT --user="$MYSQL_USER" --password="$MYSQL_PASS" --incremental --incremental-basedir="$LAST_BACKUP" "$INCREMENTALS_DIRECTORY"
74+
75+
NEW_BACKUP_DIR=$(find $INCREMENTALS_DIRECTORY -mindepth 1 -maxdepth 1 -type d -exec ls -dt {} \+ | head -1)
76+
cp $LAST_BACKUP/backup.chain $NEW_BACKUP_DIR/
77+
echo $NEW_BACKUP_DIR >> $NEW_BACKUP_DIR/backup.chain
78+
}
79+
80+
81+
#
82+
# Call before hooks
83+
#
84+
if [ -d "/hooks" ] && ls /hooks/*.before 1> /dev/null 2>&1; then
85+
for hookfile in /hooks/*.before; do
86+
eval $hookfile
87+
echo "Called hook $hookfile"
88+
done
89+
fi
90+
91+
92+
if [ "$1" = "full" ]; then
93+
full_backup
94+
elif [ "$1" = "incr" ]; then
95+
LAST_CHECKPOINTS=$(find $BACKUPS_DIRECTORY -mindepth 3 -maxdepth 3 -type f -name xtrabackup_checkpoints -exec ls -dt {} \+ | head -1)
96+
97+
if [[ -f $LAST_CHECKPOINTS ]]; then
98+
incremental_backup
99+
else
100+
full_backup
101+
fi
102+
elif [ "$1" = "list" ]; then
103+
if [[ -d $FULLS_DIRECTORY ]]; then
104+
BACKUP_CHAINS=$(ls $FULLS_DIRECTORY | wc -l)
105+
else
106+
BACKUP_CHAINS=0
107+
fi
108+
109+
if [[ $BACKUP_CHAINS -gt 0 ]]; then
110+
echo -e "Available backup chains (from oldest to latest):\n"
111+
112+
for FULL_BACKUP in `ls $FULLS_DIRECTORY -tr`; do
113+
let COUNTER=COUNTER+1
114+
115+
echo "Backup chain $COUNTER:"
116+
echo -e "\tFull: $FULL_BACKUP"
117+
118+
if [[ $(ls $INCREMENTALS_DIRECTORY | wc -l) -gt 0 ]]; then
119+
grep -l $FULL_BACKUP $INCREMENTALS_DIRECTORY/**/backup.chain | \
120+
while read INCREMENTAL;
121+
do
122+
BACKUP_DATE=${INCREMENTAL%/backup.chain}
123+
echo -e "\tIncremental: ${BACKUP_DATE##*/}"
124+
done
125+
fi
126+
done
127+
128+
LATEST_BACKUP=$(find $BACKUPS_DIRECTORY -mindepth 2 -maxdepth 2 -type d -exec ls -dt {} \+ | head -1)
129+
130+
[[ "$LATEST_BACKUP" == *full* ]] && IS_FULL=1 || IS_FULL=0
131+
132+
BACKUP_DATE=${LATEST_BACKUP##*/}
133+
134+
if [[ "$LATEST_BACKUP" == *full* ]]
135+
then
136+
echo -e "\nLatest backup available:\n\tFull: $BACKUP_DATE"
137+
else
138+
echo -e "\nLatest backup available:\n\tIncremental: $BACKUP_DATE"
139+
fi
140+
141+
exit 1
142+
else
143+
die "No backup chains available in the backup directory specified in the configuration ($BACKUPS_DIRECTORY)"
144+
fi
145+
elif [ "$1" = "restore" ]; then
146+
([ -n "$2" ] && [ -n "$3" ]) || die "Missing arguments. Please run as: \n\t$0 restore <timestamp> <destination folder>\nTo see the list of the available backups, run:\n\t$0 list"
147+
148+
BACKUP_TIMESTAMP="$2"
149+
DESTINATION="$3"
150+
BACKUP=`find $BACKUPS_DIRECTORY -mindepth 2 -maxdepth 2 -type d -name $BACKUP_TIMESTAMP -exec ls -dt {} \+ | head -1`
151+
LOG_FILE="$LOGS/restore-$BACKUP_TIMESTAMP.log"
152+
153+
echo "" > $LOG_FILE
154+
155+
(mkdir -vp $DESTINATION) || die "Could not access destination folder $3 - aborting"
156+
157+
if [[ -d "$BACKUP" ]]; then
158+
echo -e "!! About to restore MySQL backup taken on $BACKUP_TIMESTAMP to $DESTINATION !!\n"
159+
160+
if [[ "$BACKUP" == *full* ]]; then
161+
echo "- Restore of full backup taken on $BACKUP_TIMESTAMP"
162+
163+
echo "Copying data files to destination..."
164+
$RSYNC_COMMAND --quiet -ah --delete $BACKUP/ $DESTINATION &>> $LOG_FILE || fail
165+
echo -e "...done.\n"
166+
167+
echo "Preparing the destination for use with MySQL..."
168+
$INNOBACKUPEX_COMMAND --apply-log --ibbackup=xtrabackup_51 $DESTINATION &>> $LOG_FILE || fail
169+
echo -e "...done.\n"
170+
else
171+
XTRABACKUP=$(which xtrabackup)
172+
173+
[ -f "$XTRABACKUP" ] || die "xtrabackup executable not found - this is required in order to restore from incrementals. Ensure xtrabackup is installed properly - aborting."
174+
175+
XTRABACKUP_COMMAND="$(which nice) -n 15 $IONICE_COMMAND $XTRABACKUP"
176+
177+
FULL_BACKUP=$(cat $BACKUP/backup.chain | head -1)
178+
179+
echo "- Restore of base backup from $FULL_BACKUP"
180+
181+
echo "Copying data files to destination..."
182+
$RSYNC_COMMAND --quiet -ah --delete $FULL_BACKUP/ $DESTINATION &>> $LOG_FILE || fail
183+
echo -e "...done.\n"
184+
185+
echo "Preparing the base backup in the destination..."
186+
#$XTRABACKUP_COMMAND --prepare --apply-log-only --target-dir=$DESTINATION &>> $LOG_FILE || fail
187+
$INNOBACKUPEX_COMMAND --apply-log --redo-only $DESTINATION &>> $LOG_FILE || fail
188+
echo -e "...done.\n"
189+
190+
for INCREMENTAL in $(cat $BACKUP/backup.chain | tail -n +2); do
191+
echo -e "Applying incremental from $INCREMENTAL...\n"
192+
#$XTRABACKUP_COMMAND --prepare --apply-log-only --target-dir=$DESTINATION --incremental-dir=$INCREMENTAL &>> $LOG_FILE || fail
193+
$INNOBACKUPEX_COMMAND --apply-log --redo-only $DESTINATION --incremental-dir=$INCREMENTAL &>> $LOG_FILE || fail
194+
echo -e "...done.\n"
195+
done
196+
197+
echo "Finalising the destination..."
198+
#$XTRABACKUP_COMMAND --prepare --target-dir=$DESTINATION &>> $LOG_FILE || fail
199+
$INNOBACKUPEX_COMMAND --apply-log $DESTINATION &>> $LOG_FILE || fail
200+
echo -e "...done.\n"
201+
fi
202+
203+
rm $LOG_FILE # no errors, no need to keep it
204+
205+
echo -e "The destination is ready. All you need to do now is:
206+
- ensure the MySQL user owns the destination directory, e.g.: chown -R mysql:mysql $DESTINATION
207+
- stop MySQL server
208+
- replace the content of the MySQL datadir (usually /var/lib/mysql) with the content of $DESTINATION
209+
- start MySQL server again"
210+
else
211+
die "Backup not found. To see the list of the available backups, run: $0 list"
212+
fi
213+
else
214+
die "Backup type not specified. Please run: as $0 [incr|full|list|restore]"
215+
fi
216+
217+
BACKUP_CHAINS=`ls $FULLS_DIRECTORY | wc -l`
218+
219+
if [[ $BACKUP_CHAINS -gt $MAX_BACKUP_CHAINS ]]; then
220+
CHAINS_TO_DELETE=$(expr $BACKUP_CHAINS - $MAX_BACKUP_CHAINS)
221+
222+
for FULL_BACKUP in `ls $FULLS_DIRECTORY -t | tail -n $CHAINS_TO_DELETE`; do
223+
grep -l $FULLS_DIRECTORY/$FULL_BACKUP $INCREMENTALS_DIRECTORY/**/backup.chain | while read incremental; do rm -rf "${incremental%/backup.chain}"; done
224+
$IONICE_COMMAND rm -rf $FULLS_DIRECTORY/$FULL_BACKUP
225+
done
226+
fi
227+
228+
unset MYSQL_USER
229+
unset MYSQL_PASS
230+
231+
#
232+
# Call after hooks
233+
#
234+
if [ -d "/hooks" ] && ls /hooks/*.after 1> /dev/null 2>&1; then
235+
for hookfile in /hooks/*.after; do
236+
echo "===> Calling hook ${hookfile}... "
237+
eval $hookfile
238+
echo "===> Calling hook ${hookfile}... DONE"
239+
done
240+
241+
echo "===> All hooks processed, finished."
242+
else
243+
echo "===> No hooks found, finished."
244+
fi

0 commit comments

Comments
 (0)