在 Dockerfile 中设置 MySQL 并导入转储

Posted

技术标签:

【中文标题】在 Dockerfile 中设置 MySQL 并导入转储【英文标题】:Setting up MySQL and importing dump within Dockerfile 【发布时间】:2014-11-13 05:29:16 【问题描述】:

我正在尝试为我的 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 命令都在不同的容器中执行。这里解释得很好:***.com/questions/17891669/… 这只是说明 RUN 命令有不同的上下文。但我依赖的是一个守护进程,而不是上下文。 是的,但这解释了为什么您无法连接到 MySQL。这是因为,它只在您的第一行 RUN 中运行。 要执行你的SQL语句,你需要启动MySQL,并在同一个容器中使用MySQL客户端:一个RUN,分几步。您可以在此处找到一个多步骤安装软件的示例:***.com/questions/25899912/install-nvm-in-docker/… 您也可以使用 docker-compose 查看服务:docs.docker.com/compose/wordpress/#build-the-project 这样,mysql 可以附加到您的应用中 【参考方案1】:

官方mysql docker image的最新版本允许您在启动时导入数据。这是我的 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

在这里,我在 docker/data 下有我的 data-dump.sql,它与运行 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"]

数据容器甚至不必处于启动状态即可持久化。

【讨论】:

恕我直言,这是更好的答案。它允许不自己创建图像。感谢您的提示。 对卷的一个小改动:- ./docker/data:/docker-entrypoint-initdb.d — 删除了容器目录前面的 . 虽然这是正确的过程,但它并不能很好地解决用例。 Docker 的优点之一是您可以非常快速地启动环境。如果在启动期间 Docker 导入 MySQL 数据库时必须等待 3-4 分钟,那么您将失去这一优势。目标是拥有一个已经包含数据库中数据的容器,以便您可以尽快使用它。 这个答案是否仍然适用于最新版本?似乎不再起作用了 请注意这里的 docker-compose 示例是 v2 而不是 v3。 v3 不支持“volumes_from:”。【参考方案2】:

Dockerfile 中的每个 RUN 指令在不同的层中执行(如 documentation of RUN 中所述)。

在您的Dockerfile 中,您有三个RUN 指令。问题是 MySQL 服务器只在第一次启动。在其他情况下,没有 MySQL 正在运行,这就是您与 mysql 客户端连接错误的原因。

要解决这个问题,你有 2 个解决方案。

解决方案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

【讨论】:

您知道我如何保存这些信息吗?当我在容器上运行东西时,我在 Dockerfile 构建上创建的数据库和表不可用。 :( 这取决于你所说的坚持。如果您想让数据在多个docker run 执行中保持不变,您需要挂载卷。如果您只想为您的转储提供一个容器,但又不想保留进一步的修改,您可以去掉 Dockerfile 中的 VOLUME 指令。 我在尝试解决方案 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/mysqlmysqld_safe mysqld from pid file /var/run/mysqld/mysqld.pid ended 我发现在启动时进行脚本检查以查看mysql是否创建了数据库要好得多。如果有,则不要管它,否则运行mysql_init_db 并加载到您的数据库结构中。这允许在测试时自动重置数据库的灵活性,但如果您希望它持久化或测试不同的数据集,您只需使用 docker run -v ... 在 /var/lib/mysql 挂载一个卷。 @trevorgrayson - 这也不起作用。现在它给出错误 ERROR 2003 (HY000): Can't connect to MySQL server on '127.0.0.1' (111)【参考方案3】:

我所做的是将我的 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 时,转储会在数据库中恢复。

【讨论】:

+1,添加一个:指向正在运行的脚本的链接,以便更好地了解实际发生的情况:github.com/docker-library/mariadb/blob/… latest mysql 似乎没有加载转储的 sql 文件,即使使用 5.6 我也遇到了 InnoDb 存储引擎的问题。 这里一样,它有这样的指令,我什至在日志中看到正在加载初始化文件,但 db 是空的!【参考方案4】:

这是使用v3docker-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 中的文件而不删除卷,请发表评论,我会更新这个答案。

【讨论】:

这个答案对我有帮助。 docker-compose down 上的注释非常重要,因为尽管重新启动了 docker-compose,但我没有看到任何变化。在这种情况下,MYSQL_DATABASE 是必需的变量【参考方案5】:

我使用了 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 转储文件,但我在开头添加了 2 行,因此文件看起来像这样

--
-- 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 文件的方法。但是根据this,.env 文件功能仅在您使用docker-compose up 命令时有效,不适用于docker stack deploy。因此,如果您想在生产中使用.env 文件,您可能需要查看docker 17.06 引入的docker secrets。然后,您可以在开发docker compose up 和生产docker stack deploy 阶段将您的 .env 文件与秘密结合使用 不错的方法,但我建议使用RUN sed -i '1s/^/CREATE DATABASE IF NOT EXISTS $MYSQL_DATABASE;\nUSE $MYSQL_DATABASE;\n/' data.sql 而不是您建议的sed 命令。这样您就可以使用您拥有的任何转储文件 - 如果您正在处理大量数据,这将非常有用:)【参考方案6】:

编辑:我误解了这里的问题。我的以下回答解释了如何在 容器创建时间 运行 sql 命令,而不是在 图像创建时间 如 OP 所期望的那样。

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

我遵循此处的其他答案并将 bash 和 sql 脚本复制到 docker 容器内的文件夹 /docker-entrypoint-initdb.d/ 中,因为这显然是 mysql 映像提供程序的预期方式。一旦 db 守护程序准备就绪,此文件夹中的所有内容都会执行,因此您应该可以依赖它。

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

这就是我所需要的,例如,因为我还需要导入转储,但仅转储是不够的,因为它没有提供应该导入哪个数据库。所以就我而言,我有一个名为 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/

【讨论】:

【参考方案7】:

基于Kuhess response,但没有硬睡眠:

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

【讨论】:

【参考方案8】:

我也遇到过同样的问题,但通过分离 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 服务器需要几秒钟才能启动,然后才能接受传入的连接和脚本。

【讨论】:

以上是关于在 Dockerfile 中设置 MySQL 并导入转储的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Dockerfile 中设置 Node 环境变量以运行 node.js 应用程序?

dockerfile镜像脚本中设置环境变量的是哪一项

dockerfile中设置环境变量

基于 build ARG 有条件地在 Dockerfile 中设置 ENV vars

Docker:如何在 Centos 6.8 docker 文件中设置和使用 python 2.7

如何在 docker 容器中设置 flask-socketio?