MariaDB / mariadb-docker

Docker Official Image packaging for MariaDB
https://mariadb.org
GNU General Public License v2.0
755 stars 436 forks source link

Feature request: initialize container from mariadb-backup prepared tarball was: .my-healthcheck.cnf file in /var/lib/mysql causes failure of mariabackup #522

Closed mholmesbeatport closed 8 months ago

mholmesbeatport commented 11 months ago

I have ran into an issue with getting our local api development environment up and running with our mariadb 10.5 docker container.

I am successful with getting our dev environment up on versions 10.5.18, 10.5.19 and 10.5.20 but since I have 10.5 in my docker-compose file it defaults to the current 10.5.21 version of mariadb.

I have a compressed backup file and an import.sh bash shell file copied into the docker-entrypoint-initdb.d When the container starts the bash shell file is executed and does the following:

The database files are then moved into the /var/lib/mysql default data directory.

This process works flawlessly in versions <=10.5.20. In version 10.5.21 this process fails because there is now a .my-healthcheck.cnf file in the /var/lib/mysql data directory. I get the following error: "Original data directory /var/lib/mysql is not empty!"

This causes the container to be in a repeating restart loop and fails to start successfully.

This is the docker logs with some added [DEBUG] statements:

2023-07-18 16:32:17-06:00 [Note] [Entrypoint]: /usr/local/bin/docker-entrypoint.sh: running /docker-entrypoint-initdb.d/import.sh
/docker-entrypoint-initdb.d/backup.zst: 284754608 bytes
[DEBUG] All /var/lib/mysql files should now be removed
[DEBUG] Will now check if /var/lib/mysql folder is empty:
Not Empty
[DEBUG] If folder is not empty list files in the folder:
Found file /var/lib/mysql/.my-healthcheck.cnf
[DEBUG] Beginning mariabackup prepare...
mariabackup based on MariaDB server 10.5.21-MariaDB debian-linux-gnu (x86_64)
[00] 2023-07-18 16:32:17 cd to /tmp/
[00] 2023-07-18 16:32:17 open files limit requested 0, set to 1048576
[00] 2023-07-18 16:32:17 This target seems to be not prepared yet.
[00] 2023-07-18 16:32:17 mariabackup: using the following InnoDB configuration for recovery:
[00] 2023-07-18 16:32:17 innodb_data_home_dir = .
[00] 2023-07-18 16:32:17 innodb_data_file_path = ibdata1:12M:autoextend
[00] 2023-07-18 16:32:17 innodb_log_group_home_dir = .
[00] 2023-07-18 16:32:17 InnoDB: Using Linux native AIO
[00] 2023-07-18 16:32:17 Starting InnoDB instance for recovery.
[00] 2023-07-18 16:32:17 mariabackup: Using 104857600 bytes for buffer pool (set by --use-memory parameter)
2023-07-18 16:32:17 0 [Note] InnoDB: Uses event mutexes
2023-07-18 16:32:17 0 [Note] InnoDB: Compressed tables use zlib 1.2.11
2023-07-18 16:32:17 0 [Note] InnoDB: Number of pools: 1
2023-07-18 16:32:17 0 [Note] InnoDB: Using crc32 + pclmulqdq instructions
2023-07-18 16:32:17 0 [Note] InnoDB: Using Linux native AIO
2023-07-18 16:32:17 0 [Note] InnoDB: Initializing buffer pool, total size = 104857600, chunk size = 104857600
2023-07-18 16:32:17 0 [Note] InnoDB: Completed initialization of buffer pool
2023-07-18 16:32:17 0 [Note] InnoDB: Starting crash recovery from checkpoint LSN=74158708,74158708
[00] 2023-07-18 16:32:17 Last binlog file , position 0
[00] 2023-07-18 16:32:18 completed OK!
[DEBUG] Beginning mariabackup move-back...
mariabackup based on MariaDB server 10.5.21-MariaDB debian-linux-gnu (x86_64)
[00] 2023-07-18 16:32:18 Original data directory /var/lib/mysql is not empty!
[DEBUG] End of import.sh file

I see the .my-healthcheck.cnffile has been recently implemented approximately 3 weeks ago with the following commit: https://github.com/MariaDB/mariadb-docker/pull/508/files#diff-7325bb1235108b46404c9b33b577ca3033e5d8b65a506b657e41ebccb96fcb5f

Mentioned in the following GitHub issue: https://github.com/MariaDB/mariadb-docker/issues/512#issuecomment-1608744838

As stated in MariaDB documentation in order to restore a backup the data directory needs to be empty: https://mariadb.com/kb/en/full-backup-and-restore-with-mariabackup/#restoring-the-backup

Any advice on how to work around this is much appreciated!

grooverdan commented 11 months ago

The problem with rm -rf /var/lib/mysql/** is it ignore .* files as below (and see man bash):

$ mkdir mysql

/tmp 
$ touch mysql/.my_healthcheck.cnf

/tmp 
$ rm -rf mysql/**

/tmp 
$ ls -la mysql
total 0
drwxr-xr-x.  2 dan  dan   60 Jul 19 13:34 .
drwxrwxrwt. 29 root root 820 Jul 19 13:34 ..
-rw-r--r--.  1 dan  dan    0 Jul 19 13:34 .my_healthcheck.cnf

/tmp 
$ man rm

/tmp 
$ rm -rf mysql/** mysql/.*

/tmp 
$ ls -la mysql
total 0
drwxr-xr-x.  2 dan  dan   40 Jul 19 13:36 .
drwxrwxrwt. 29 root root 820 Jul 19 13:36 ..

So to remove you'll need rm -rf /var/lib/mysql/** /var/lib/mysql/.*

Alternately enable shopt -s dotglob like:

$ shopt -s dotglob

/tmp 
$ ls -la mysql
total 0
drwxr-xr-x.  2 dan  dan   60 Jul 19 13:40 .
drwxrwxrwt. 29 root root 820 Jul 19 13:42 ..
-rw-r--r--.  1 dan  dan    0 Jul 19 13:40 .my_healthcheck.cnf

/tmp 
$ rm -rf mysql/**

/tmp 
$ ls -la mysql
total 0
drwxr-xr-x.  2 dan  dan   40 Jul 19 13:42 .
drwxrwxrwt. 29 root root 820 Jul 19 13:42 ..

That said, restoring from a full backup in /docker-entrypoint-initdb.d is a pretty common use case and requiring users like yourself to script this manual isn't a great experience. You're pretty luck your data fits in /tmp but larger restores may not be as lucky and --move-back across filesystems is a fully copy again.

If you can come up with a good well known name mariadb-initdb{something}{compressext} (and format - prepare needs to be already done, so just tar?) that if it exists in /docker-entrypoint-initdb.d, and the datadir is empty, its used for the database initialization directly.

I also recommend applying prepare to before the backup was made as a version bump on the mariadb container may not support the prepare stage of the previous backup.

mholmesbeatport commented 11 months ago

@grooverdan thank you for your recommendation. Adding the mysql/.* to my remove statement has unblocked me.

I appreciate your suggestions on modifying how we are going about the restore of the backup and will look to make the modifications above to improve the process and make it less manual.

Just to ensure I understand the process you are proposing does the following sound correct:

  1. run a mariabackup of database
  2. run a mariabackup --prepare
  3. Compress to a tar file with naming convention (for example sake: mariadb-initdb-mybackup.tar )
  4. COPY tar file to /docker-entrypoint-initdb.d
  5. Database is initialized directly on container startup

Let me know if I have misunderstood any steps

grooverdan commented 11 months ago

Pretty much.

  1. Would be a fixed filename (omit -mybackup) as it doesn't really make sense to support more than one. (except Aria, maybe, consider later). Or should I just glob *.tar.* and do them all? Supporting a standard range of compression tools available in container is easy extension, especially with tar --auto-compress.
  2. Could just be a volume mount rather than a copy.

I think I can use mariadb-backup --move-back on detection of some mariadb-backup meta files and otherwise just assume its a full datadir.

shopt -s gotglob
for f in *.tar.*; do
mkdir -f $DATADIR/.init
tar --auto-compress -x $f -C --directory=$DATADIR/.init
if [ -f $DATADIR/.init/xtra* ]; then 
  gosu $USER mariabackup --target-dir=/var/lib/mysql/.init --datadir=/var/lib/mysql/.restore --move-back

  mv $DATADIR/.restore/** $DATADIR/
else
  mv $DATADIR/.init/** $DATADIR/
  chown $USER: -R $DATADIR/
fi
  1. continue initialization of other files.
  2. proceed with MARIADB_AUTO_UPGRADE=1 if set

Did you want to try to implement the entrypoint changes and contribute a pull request?

mholmesbeatport commented 11 months ago

I have not contributed to an open source project before so I would be lying if I didn't say I am a wee bit intimidated by the prospect. But I think I would like to give it a try. I can see it being a really helpful option for a full backup restore. I will read the documentation on getting set up to contribute and get started.

grooverdan commented 11 months ago

Thanks for being willing. open source contributions require people to make it work. I've just got a few too many commitments this week so won't be able to give time to doing this as soon as you'd like. I really appreciate you trying and are going to test what you have well before it gets merged.

Quick guide from github on mechanics of contributing: https://docs.github.com/en/get-started/quickstart/contributing-to-projects

For this will require a change to the docker-entrypoint.sh on the top of the project.

To build/test this

./update.sh 10.6
docker buildx --tag mtest 10.6

Pick a version your backup relates to. I tend to use buildah --tag mtest 10.6 to the same effect but anything that builds from a Dockerfile.

Do a manual test:

docker run -v /path/to/xxx.tar.gz:/docker-entrypoint-initdb.d:z mtest

An easier to trace version is:

docker run -v /path/to/xxx.tar.gz:/docker-entrypoint-initdb.d:z --user mysql mtest bash -x -v docker-entrypoint.sh

If you feeling especially keen, there's a test suite in .test/run.sh that can be appended to at the bottom. Execute with .test/run.sh mtest THE_TEST_NAME.

Some specific rules/tests to be implemented checked:

When there is a *.tar.*:

grooverdan commented 10 months ago

@mholmesbeatport have you had a chance to attempt this?

mholmesbeatport commented 10 months ago

@grooverdan I have not yet had the time to work on this. However, I have hit the exact issue you said may happen - I have now ran out of disk space for that restore using our previous method. I will need to move on this ticket in the very near future.

dr-m commented 10 months ago

I think that some version of rm might not refuse to remove .. (the parent directory), and therefore rm -fr .* would be safer to write as rm -fr .[^.]* ..[^.]* (to remove all files or directories that start with . except for ..). Also the expansions of the glob pattern directoryname/.* should include directoryname/.. when using a POSIX /bin/sh compatible shell like dash . In bash there is a shell option globskipdots that is set at least on my system according to shopt -p:

shopt -s globskipdots
grooverdan commented 9 months ago

Hi, there's a draft of loading mariabackup in #533. If you kindly download, build your own container and tell me if it suits your purposes (or not).

mholmesbeatport commented 9 months ago

Thank you @grooverdan. Sorry work has been busy and I was not able to contribute on this as I had hoped. Thank you for picking this up. I will likely only get to testing this on Friday but will post back when I have.

grooverdan commented 8 months ago

ok merged now. Testing still welcome. Still a week before release.