在Dockerfile中设置MySQL并导入数据转储

151

我正在尝试为我的LAMP项目设置Dockerfile,但是启动MySQL时出现了一些问题。 我的Dockerfile中有以下几行:

VOLUME ["/etc/mysql", "/var/lib/mysql"]
ADD dump.sql /tmp/dump.sql
RUN /usr/bin/mysqld_safe & sleep 5s
RUN mysql -u root -e "CREATE DATABASE mydb"
RUN mysql -u root mydb < /tmp/dump.sql

但我一直收到这个错误信息:
ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock' (111)

有没有关于如何在Dockerfile构建期间设置数据库创建和导入转储的想法?

这是因为每个RUN命令在不同的容器中执行。这里有很好的解释:https://dev59.com/JWMm5IYBdhLWcg3wFL04 - Kuhess
这只是解释了RUN命令具有不同的上下文。但我依赖于守护进程,而不是上下文。 - vinnylinux
1
是的,但这解释了为什么您无法连接到MySQL。因为它只在您的第一个“RUN”行中运行。 - Kuhess
要执行SQL语句,您需要启动MySQL,并在同一个容器中使用MySQL客户端:一个RUN带有多个步骤。您可以在此处找到一个软件的多步安装示例:https://dev59.com/G18e5IYBdhLWcg3wRY0k#25900207 - Kuhess
你也可以使用docker-compose服务:https://docs.docker.com/compose/wordpress/#build-the-project,通过这个服务,mysql可以附加到你的应用程序上。 - aurny2420289
我知道这不是你要求的,但你也可以使用docker cp将文件复制到容器中,这非常方便:https://dev59.com/FmAh5IYBdhLWcg3wBfeM#31971697 - Braden Holt
9个回答

188

官方 mysql docker 镜像 的最新版本允许你在启动时导入数据。这是我的 docker-compose.yml 文件

data:
  build: docker/data/.
mysql:
  image: mysql
  ports:
    - "3307:3306"
  environment:
    MYSQL_ROOT_PASSWORD: 1234
  volumes:
    - ./docker/data:/docker-entrypoint-initdb.d
  volumes_from:
    - data

这里,我有一个叫做data-dump.sql的文件,它位于docker/data目录下,该目录是相对于运行docker-compose的文件夹而言的。我将该sql文件挂载到容器中的/docker-entrypoint-initdb.d目录中。

如果你想了解它是如何工作的,可以查看他们在GitHub上的docker-entrypoint.sh。他们添加了这个块来允许导入数据。

    echo
    for f in /docker-entrypoint-initdb.d/*; do
        case "$f" in
            *.sh)  echo "$0: running $f"; . "$f" ;;
            *.sql) echo "$0: running $f"; "${mysql[@]}" < "$f" && echo ;;
            *)     echo "$0: ignoring $f" ;;
        esac
        echo
    done

另外需要注意,如果您希望在停止或删除mysql容器后仍要保留数据,则需要像docker-compose.yml中所示,使用单独的数据容器。数据容器的Dockerfile非常简单。

FROM n3ziniuka5/ubuntu-oracle-jdk:14.04-JDK8

VOLUME /var/lib/mysql

CMD ["true"]
数据容器甚至不需要处于起始状态以进行持久化。

1
这是我个人认为更好的答案。它允许我们不必自己创建图像。感谢这个提示。 - Metal3d
5
对于卷来说只有一个小改动:从容器目录前面移除.,改为- ./docker/data:/docker-entrypoint-initdb.d - David Sinclair
7
虽然这是正确的程序,但它并不能很好地解决使用情况。Docker 的优点之一是你可以快速启动一个环境。如果你在启动期间等待 3-4 分钟,让 Docker 导入 MySQL 数据库,那么你就失去了这个优势。目标是拥有一个包含数据库数据的容器,以便尽快使用它。 - Garreth McDaid
3
这个答案在最新版本中仍然适用吗?看起来不再起作用了。 - Daniel
2
请注意,此处的docker-compose示例为v2而非v3。在v3中不支持"volumes_from:"。 - Ernest
显示剩余3条评论

137

每个 Dockerfile 中的 RUN 指令都在不同的层中执行(如 RUN 文档 中所解释的)。

在您的 Dockerfile 中,有三个 RUN 指令。问题是 MySQL 服务器仅在第一个指令中启动。在其他指令中,没有任何运行的 MySQL,这就是为什么你使用 mysql 客户端时会出现连接错误。

要解决此问题,您有两个解决方案。

解决方案 1:使用单行 RUN

RUN /bin/bash -c "/usr/bin/mysqld_safe --skip-grant-tables &" && \
  sleep 5 && \
  mysql -u root -e "CREATE DATABASE mydb" && \
  mysql -u root mydb < /tmp/dump.sql

解决方案2:使用脚本

创建可执行脚本init_db.sh

#!/bin/bash
/usr/bin/mysqld_safe --skip-grant-tables &
sleep 5
mysql -u root -e "CREATE DATABASE mydb"
mysql -u root mydb < /tmp/dump.sql
将这些行添加到您的Dockerfile中:
ADD init_db.sh /tmp/init_db.sh
RUN /tmp/init_db.sh

很奇怪,你无法在容器中看到你的数据库和表。尝试使用 docker build -t mysql . 进行构建,然后运行 docker run -t -i mysql /bin/bash。在这里,你应该能够通过 mysql 客户端看到所有内容。 - Kuhess
1
这取决于您对“持久化”的理解。如果您希望在多个 docker run 执行之间保留数据,则需要挂载卷。如果您只想拥有一个包含转储的容器,但不想保留进一步的修改,则可以在您的 Dockerfile 中删除 VOLUME 指令。 - Kuhess
19
尝试解决方案1时,我遇到了“ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock' (2)”的错误。根据日志信息,“mysqld_safe Starting mysqld daemon with databases from /var/lib/mysql”和“mysqld_safe mysqld from pid file /var/run/mysqld/mysqld.pid ended”。 - Victor Basso
1
我发现在启动时运行一个脚本来检查mysql是否已经创建了数据库会更好。如果已经创建,则不做任何操作,否则运行mysql_init_db并加载您的数据库结构。这样可以灵活地在测试时自动重置数据库,但如果您希望它持久化或测试不同的数据集,则只需使用docker run -v ...在/var/lib/mysql上挂载卷即可。 - tudor -Reinstate Monica-
1
@trevorgrayson - 那也不行。现在它显示错误 ERROR 2003 (HY000): 无法连接到 '127.0.0.1' 上的 MySQL 服务器 (111)。 - Deep
显示剩余9条评论

42

我所做的是将我的SQL转储文件下载到一个名为“db-dump”的文件夹中,并挂载该文件:

mysql:
 image: mysql:5.6
 environment:
   MYSQL_ROOT_PASSWORD: pass
 ports:
   - 3306:3306
 volumes:
   - ./db-dump:/docker-entrypoint-initdb.d

当我第一次运行docker-compose up时,转储数据被恢复到数据库中。


3
+1,再加一个:为了更好地理解实际发生的情况,附上正在运行的脚本链接: https://github.com/docker-library/mariadb/blob/d969a465ee48fe10f4b532276f7337ddaaf3fc36/10.1/docker-entrypoint.sh - Tom Imrei
8
最新的 mysql 似乎无法加载导出的 SQL 文件,即使使用 5.6 版本,我在使用 InnoDb 存储引擎时也遇到了问题。 - zinking
2
同样在这里,它有这样的指令,我甚至在日志中看到它正在加载初始化文件,但数据库是空的! - holms

16

这里是使用 v3 版本的 docker-compose.yml 的工作版本。关键是使用 volumes 指令:

mysql:
  image: mysql:5.6
  ports:
    - "3306:3306"
  environment:
    MYSQL_ROOT_PASSWORD: root
    MYSQL_USER: theusername
    MYSQL_PASSWORD: thepw
    MYSQL_DATABASE: mydb
  volumes:
    - ./data:/docker-entrypoint-initdb.d
在我拥有docker-compose.yml的目录中,我有一个data目录,其中包含.sql转储文件。这很好,因为你可以每个表都有一个.sql转储文件。
我只需运行docker-compose up,就可以开始了。数据会自动在停止之间持续存在。如果您想删除数据并"吸入"新的.sql文件,请运行docker-compose down,然后再运行docker-compose up
如果有人知道如何让mysql docker重新处理/docker-entrypoint-initdb.d中的文件而不删除卷,请留言,我将更新此答案。

3
这个回答对我很有帮助。关于 docker-compose down 的注释非常重要,因为尽管我重启了 docker-compose,但我仍然看不到任何更改。在这种情况下,MYSQL_DATABASE 是一个必需的变量。 - nxmohamad

13

我使用了docker-entrypoint-initdb.d方法(感谢@Kuhess)。但在我的情况下,我想基于我在.env文件中定义的一些参数创建我的数据库,所以我这样做了:

1)首先,在我的Docker根项目目录中定义.env文件,类似于以下内容:

MYSQL_DATABASE=my_db_name
MYSQL_USER=user_test
MYSQL_PASSWORD=test
MYSQL_ROOT_PASSWORD=test
MYSQL_PORT=3306

2) 接着我定义了我的docker-compose.yml文件。我使用args指令来定义我的环境变量,并从.env文件中设置它们。

version: '2'
services:
### MySQL Container
    mysql:
        build:
            context: ./mysql
            args:
                - MYSQL_DATABASE=${MYSQL_DATABASE}
                - MYSQL_USER=${MYSQL_USER}
                - MYSQL_PASSWORD=${MYSQL_PASSWORD}
                - MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
        ports:
            - "${MYSQL_PORT}:3306"

3) 接着我定义了一个包含Dockerfile的mysql文件夹。这个Dockerfile如下:

FROM mysql:5.7
RUN chown -R mysql:root /var/lib/mysql/

ARG MYSQL_DATABASE
ARG MYSQL_USER
ARG MYSQL_PASSWORD
ARG MYSQL_ROOT_PASSWORD

ENV MYSQL_DATABASE=$MYSQL_DATABASE
ENV MYSQL_USER=$MYSQL_USER
ENV MYSQL_PASSWORD=$MYSQL_PASSWORD
ENV MYSQL_ROOT_PASSWORD=$MYSQL_ROOT_PASSWORD

ADD data.sql /etc/mysql/data.sql
RUN sed -i 's/MYSQL_DATABASE/'$MYSQL_DATABASE'/g' /etc/mysql/data.sql
RUN cp /etc/mysql/data.sql /docker-entrypoint-initdb.d

EXPOSE 3306

4) 现在我使用mysqldump来导出我的数据库,并将data.sql文件放入mysql文件夹中。

mysqldump -h <server name> -u<user> -p <db name> > data.sql

这个文件只是一个普通的 SQL 转储文件,但我在开头添加了两行内容,使得文件看起来像这样:

--
-- Create a database using `MYSQL_DATABASE` placeholder
--
CREATE DATABASE IF NOT EXISTS `MYSQL_DATABASE`;
USE `MYSQL_DATABASE`;

-- Rest of queries
DROP TABLE IF EXISTS `x`;
CREATE TABLE `x` (..)
LOCK TABLES `x` WRITE;
INSERT INTO `x` VALUES ...;
...
...
...

所发生的情况是,我使用了“RUN sed -i 's/MYSQL_DATABASE/'$MYSQL_DATABASE'/g' /etc/mysql/data.sql”命令来替换MYSQL_DATABASE占位符为我在.env文件中设置的数据库名称。

|- docker-compose.yml
|- .env
|- mysql
     |- Dockerfile
     |- data.sql
现在您已经准备好构建和运行您的容器。

我喜欢使用.env文件的方法。然而,根据这里所述,.env文件功能仅在使用docker-compose up命令时起作用,而不适用于docker stack deploy。因此,如果您想在生产中使用.env文件,您可能需要查看docker secrets,它是在docker 17.06中引入的。然后,您可以在开发docker compose up和生产docker stack deploy阶段中将您的.env文件与secrets结合使用。 - ira
不错的方法,但我建议使用RUN sed -i '1s/^/CREATE DATABASE IF NOT EXISTS $MYSQL_DATABASE;\nUSE $MYSQL_DATABASE;\n/' data.sql代替你建议的 sed 命令。这样您可以使用任何转储文件 - 如果您处理大量数据,则非常有用 :) - Tomasz Kapłoński

1

参考Kuhess的回答,但不使用硬休眠:

RUN /bin/bash -c "/usr/bin/mysqld_safe --skip-grant-tables &" && \
  while ! mysqladmin ping --silent; do sleep 1; echo "wait 1 second"; done && \
  mysql -u root -e "CREATE DATABASE mydb" && \
  mysql -u root mydb < /tmp/dump.sql

1

编辑:我在这里误解了问题。我的下面的答案解释了如何在容器创建时运行sql命令,而不是像OP所需的镜像创建时

我对Kuhess的被接受的答案并不是很喜欢,因为sleep 5对我来说似乎有点黑客式,因为它假定mysql db守护程序已在此时间范围内正确加载。这是一个假设,而不是保证。此外,如果您使用提供的mysql docker映像,则映像本身已经负责启动服务器;我不会用自定义的/usr/bin/mysqld_safe干扰这个过程。

我遵循了这里其他答案的建议,并将bash和sql脚本复制到docker容器中的文件夹/docker-entrypoint-initdb.d/中,因为这显然是mysql映像提供者预期的方式。这个文件夹中的所有内容都会在db守护进程准备就绪后执行,因此你应该能够依赖它。

作为其他答案的补充 - 因为没有其他答案明确提到这一点:除了sql脚本,您还可以复制bash脚本到该文件夹中,这可能会给您带来更多的控制。

这就是我需要的例子,因为我还需要导入一个dump文件,但是仅仅导入dump文件是不够的,因为它没有提供应该导入到哪个数据库中。所以在我的情况下,我有一个名为db_custom_init.sh的脚本,其内容如下:

mysql -u root -p$MYSQL_ROOT_PASSWORD -e 'create database my_database_to_import_into'
mysql -u root -p$MYSQL_ROOT_PASSWORD my_database_to_import_into < /home/db_dump.sql


这个 Dockerfile 复制了那个脚本:
FROM mysql/mysql-server:5.5.62
ENV MYSQL_ROOT_PASSWORD=XXXXX
COPY ./db_dump.sql /home/db_dump.sql
COPY ./db_custom_init.sh /docker-entrypoint-initdb.d/


0
任何添加到/docker-entrypoint-initdb.d的文件或脚本都将在容器启动时执行。请确保您不会从Dockerfile中添加或运行任何可以使用mysql服务的sql或sh文件。它们将失败并停止镜像构建,因为当这些文件或脚本被调用时,mysql服务尚未启动。添加.sh文件的最佳方法是从Dockerfile将它们添加到/docker-entrypoint-initdb.d目录中。
工作示例:
FROM mysql
ADD mysqlcode.sh /docker-entrypoint-initdb.d/mysqlcode.sh
ADD db.sql /home/db.sql
RUN chmod -R 775 /docker-entrypoint-initdb.d
ENV MYSQL_ROOT_PASSWORD mypassword

而mysqlcode.sh将在mysql服务处于活动状态时执行一些命令

mysqlcode.sh

#!/bin/bash
mysql -u root -pmypassword --execute "CREATE DATABASE IF NOT EXISTS mydatabase;"
mysql -u root -pmypassword mydatabase < /home/db.sql    

-1

我曾经遇到过同样的问题,但通过分离MySQL启动命令,成功地解决了它:

sudo docker build -t MyDB_img -f Dockerfile.dev
sudo docker run --name SomeDB -e MYSQL_ROOT_PASSWORD="WhatEver" -p 3306:3306 -v $(pwd):/app -d MyDB_img

在运行MySQL脚本之前休眠20秒,这样就可以正常工作了。

sudo docker exec -it SomeDB sh -c yourscript.sh

我只能推测MySQL服务器启动需要几秒钟才能接受传入的连接和脚本。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接