【Docker】在尝试执行MariaDB的初始化SQL时遇到了困难的故事
这篇帖子是《格倫吉(Glenji)2021年圣诞日历》的第7天文章。
你好,我在株式会社格伦吉担任服务器端工程师。
我是新毕业的岛田(@konnyaku256)。
最近,我有机会使用Docker版MariaDB,并在此过程中遇到了初始化SQL的问题。在本文中,我将总结一下这个故事。
关于MariaDB的初始化
当容器首次启动时,将创建一个具有指定名称的新数据库,并使用提供的配置变量进行初始化。此外,它将执行在/docker-entrypoint-initdb.d目录中找到的具有扩展名.sh,.sql,.sql.gz,.sql.xz和.sql.zst的文件。文件将按字母顺序执行。没有文件执行权限的.sh文件将被源码而不是执行。您可以通过将SQL转储挂载到该目录并提供贡献数据的自定义映像来轻松填充您的mariadb服务。默认情况下,SQL文件将导入到由MARIADB_DATABASE / MYSQL_DATABASE变量指定的数据库中。
引用自 https://hub.docker.com/_/mariadb
根据Docker Hub的说明,Docker版MariaDB可以在/docker-entrypoint-initdb.d目录下放置.sql、.sh、.sql.gz文件,在容器启动时会读取并执行这些文件。
如果使用这个机制,似乎可以执行一些初始化处理,例如创建数据库和用户等。
然而… ‘ér…)
SQL的初始化没有被执行。
使用以下配置创建映像并启动容器。
目录结构
$ tree
.
├── Dockerfile
└── init
└── init.sql
初始化SQL(最后一行是换行符)
CREATE DATABASE `test`;
Dockerfile 的中文翻译选项:容器文件。
FROM mariadb:5.5.40
COPY init/* /docker-entrypoint-initdb.d/
CMD ["mysqld"]
乍一看,看起来似乎很不错。
我会构建映像并启动容器来尝试。
$ docker build -t mariadb-example ./ && docker run -d -e MYSQL_ROOT_PASSWORD=password --name mariadb-example mariadb-example
我会检查容器的状态。
$ docker ps -a --no-trunc -f name=mariadb-example
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
6395ad036dfa74597853a2cf1e62d73f30b7446547d2a262a887dc1614c47b2c mariadb-example "/docker-entrypoint.sh mysqld" 6 minutes ago Up 6 minutes 3306/tcp mariadb-example
从状态来看,似乎能够成功启动。
登录容器并查看详细信息。
$ docker exec -it mariadb-example /bin/bash
我要登录MariaDB数据库。
root@0b4910572e26:/# mysql -ppassword
获取数据库列表,并确认是否创建了名为test的数据库。
MariaDB [(none)]> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
+--------------------+
3 rows in set (0.00 sec)
喔!? 初始化SQL中没有写入数据库!
原因是docker-entrypoint.sh
从”docker ps”的结果中可以看出,我们创建的MariaDB容器在启动时执行了”docker-entrypoint.sh”。
COMMAND
"/docker-entrypoint.sh mysqld"
简而言之,查看 docker-entrypoint.sh 可以了解为什么初始化 SQL 未被执行。
我要检查Shell脚本的内容。
root@0b4910572e26:/# cat docker-entrypoint.sh
#!/bin/bash
set -e
if [ ! -d '/var/lib/mysql/mysql' -a "${1%_safe}" = 'mysqld' ]; then
if [ -z "$MYSQL_ROOT_PASSWORD" ]; then
echo >&2 'error: database is uninitialized and MYSQL_ROOT_PASSWORD not set'
echo >&2 ' Did you forget to add -e MYSQL_ROOT_PASSWORD=... ?'
exit 1
fi
mysql_install_db --user=mysql --datadir=/var/lib/mysql
# These statements _must_ be on individual lines, and _must_ end with
# semicolons (no line breaks or comments are permitted).
# TODO proper SQL escaping on ALL the things D:
TEMP_FILE='/tmp/mysql-first-time.sql'
cat > "$TEMP_FILE" <<-EOSQL
DELETE FROM mysql.user ;
CREATE USER 'root'@'%' IDENTIFIED BY '${MYSQL_ROOT_PASSWORD}' ;
GRANT ALL ON *.* TO 'root'@'%' WITH GRANT OPTION ;
DROP DATABASE IF EXISTS test ;
EOSQL
if [ "$MYSQL_DATABASE" ]; then
echo "CREATE DATABASE IF NOT EXISTS $MYSQL_DATABASE ;" >> "$TEMP_FILE"
fi
if [ "$MYSQL_USER" -a "$MYSQL_PASSWORD" ]; then
echo "CREATE USER '$MYSQL_USER'@'%' IDENTIFIED BY '$MYSQL_PASSWORD' ;" >> "$TEMP_FILE"
if [ "$MYSQL_DATABASE" ]; then
echo "GRANT ALL ON $MYSQL_DATABASE.* TO '$MYSQL_USER'@'%' ;" >> "$TEMP_FILE"
fi
fi
echo 'FLUSH PRIVILEGES ;' >> "$TEMP_FILE"
set -- "$@" --init-file="$TEMP_FILE"
fi
chown -R mysql:mysql /var/lib/mysql
exec "$@"
我理解了之前配置的初始化SQL语句为何没有执行。
在/docker-entrypoint-initdb.d目录下没有找到任何处理。
虽然我想说官方文档是在撒谎,但很可能在这个版本发布时还未实现使用/docker-entrypoint-initdb.d目录进行初始化的功能。
替换docker-entrypoint.sh
由于发现无法使用`/docker-entrypoint-initdb.d`,我们决定考虑替代方案。
这次,我们决定快速替换`docker-entrypoint.sh`。
目录结构
$ tree
.
├── Dockerfile
├── docker-entrypoint.sh
└── init
└── init.sql
我們將添加以下類似的docker-entrypoint.sh。
#!/bin/bash
set -e
if [ ! -d '/var/lib/mysql/mysql' -a "${1%_safe}" = 'mysqld' ]; then
if [ -z "$MYSQL_ROOT_PASSWORD" ]; then
echo >&2 'error: database is uninitialized and MYSQL_ROOT_PASSWORD not set'
echo >&2 ' Did you forget to add -e MYSQL_ROOT_PASSWORD=... ?'
exit 1
fi
mysql_install_db --user=mysql --datadir=/var/lib/mysql
# These statements _must_ be on individual lines, and _must_ end with
# semicolons (no line breaks or comments are permitted).
# TODO proper SQL escaping on ALL the things D:
TEMP_FILE='/tmp/mysql-first-time.sql'
cat > "$TEMP_FILE" <<-EOSQL
DELETE FROM mysql.user ;
CREATE USER 'root'@'%' IDENTIFIED BY '${MYSQL_ROOT_PASSWORD}' ;
GRANT ALL ON *.* TO 'root'@'%' WITH GRANT OPTION ;
DROP DATABASE IF EXISTS test ;
EOSQL
if [ "$MYSQL_DATABASE" ]; then
echo "CREATE DATABASE IF NOT EXISTS $MYSQL_DATABASE ;" >> "$TEMP_FILE"
fi
if [ "$MYSQL_USER" -a "$MYSQL_PASSWORD" ]; then
echo "CREATE USER '$MYSQL_USER'@'%' IDENTIFIED BY '$MYSQL_PASSWORD' ;" >> "$TEMP_FILE"
if [ "$MYSQL_DATABASE" ]; then
echo "GRANT ALL ON $MYSQL_DATABASE.* TO '$MYSQL_USER'@'%' ;" >> "$TEMP_FILE"
fi
fi
# 初期化SQLを実行
cat ./initdb/init.sql >> "$TEMP_FILE"
echo 'FLUSH PRIVILEGES ;' >> "$TEMP_FILE"
set -- "$@" --init-file="$TEMP_FILE"
fi
chown -R mysql:mysql /var/lib/mysql
exec "$@"
与替换前的不同在于以下一行。
在现有的Shell脚本中,似乎使用变量$TEMP_FILE进行数据库的初始化工作,因此我将初始化SQL的内容追加到$TEMP_FILE的末尾。
# 初期化SQLを実行
cat ./initdb/init.sql >> "$TEMP_FILE"
将文件的权限更改为755。
$ chmod 755 docker-entrypoint.sh
我們將Dockerfile進行以下修正。
FROM mariadb:5.5.40
COPY init/* /initdb/
COPY docker-entrypoint.sh /
CMD ["mysqld"]
重新执行从构建镜像到登录MariaDB的步骤。
获取数据库列表,并检查是否创建了名为”test”的数据库。
MariaDB [(none)]> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| test |
+--------------------+
4 rows in set (0.00 sec)
这次制作得很棒!?
总结
-
- Docker版 MariaDB 5.5.40(かなり古い)では /docker-entrypoint-initdb.d ディレクトリにsqlファイルを配置しても初期化を実行できなかった
- 代わりに docker-entrypoint.sh を置き換えて、初期化SQLが実行されるようにした
因为结构相似,所以即使在MySQL的旧版本中也可能会遇到类似的问题。
虽然很少有机会遇到这种情况,但如果能对某人有所帮助那就很幸运了。
明天gamu先生的文章将会发布。敬请期待!
给出的参考
-
- https://hub.docker.com/_/mariadb
- https://qiita.com/NagaokaKenichi/items/ae037963b33a85df33f5