【问题标题】:How to create populated MySQL Docker Image on build time如何在构建时创建填充的 MySQL Docker 映像
【发布时间】:2015-09-09 14:48:59
【问题描述】:

我想创建一个已填充数据的 MySQL Docker 映像。

我想像这样创建 3 层:

        |---------------------|---------------------|
Layer 3 | Customer 1 Database | Customer 2 Database |
        |---------------------|---------------------|
Layer 2 |   Database image with tables but no data  |
        |-------------------------------------------|
Layer 1 |                mysql:5.6.26               |
        |-------------------------------------------|

我现在的问题是如何为第 2 层和第 3 层创建正确的 Dockerfile? 我的 empty_with_tables.sql 文件被加载到第 2 层,customer1.sql 和 customer2.sql 被加载到第 3 层的两个图像中。我读过一些关于将 SQL 文件放入“/docker-entrypoint-initdb.d”的内容。但这会导致数据是第一次启动图像时的数据。这不是我想要的。我希望数据在图像中准备好(例如在测试中快速可用)。

我可以启动 mysql 映像,从命令行加载数据并执行“提交”,但这不可重现,需要在 SQL 文件中的数据更改时再次执行此操作。

如何做到这一点?

【问题讨论】:

    标签: mysql database docker


    【解决方案1】:

    这周我遇到了同样的问题。我找到了一个不需要--volumes-from的可行解决方案@

    已经说明的问题是/var/lib/mysql 是一个卷,并且由于 Docker 在不久的将来不会在它的 Dockerfile 中支持UNVOLUME,如果你愿意,你不能使用这个位置来存储你的数据库默认情况下从空数据库开始。 (https://github.com/docker/docker/issues/18287)。这就是为什么我覆盖etc/mysqld.my.cnf,给mysql一个新的datadir。

    加上 pwes 的回答,您可以像这样创建一个 Dockerile:

    FROM mysql:5.6
    
    ENV MYSQL_DATABASE db
    ENV MYSQL_ROOT_PASSWORD pass
    COPY db.sql /docker-entrypoint-initdb.d/db.sql
    COPY my.cnf /etc/mysql/my.cnf
    RUN /entrypoint.sh mysqld & sleep 30 && killall mysqld
    RUN rm /docker-entrypoint-initdb.d/db.sql
    

    my.cnf 中唯一的变化是数据目录的位置:

    .... 
    [mysqld]
    skip-host-cache
    skip-name-resolve
    user        = mysql
    pid-file    = /var/run/mysqld/mysqld.pid
    socket      = /var/run/mysqld/mysqld.sock
    port        = 3306
    basedir     = /usr
    datadir     = /var/lib/mysql2 <-- can be anything except /var/lib/mysql
    tmpdir      = /tmp
    lc-messages-dir = /usr/share/mysql
    explicit_defaults_for_timestamp
    ....
    

    【讨论】:

    • 以下所有内容(包括以下修改),当我尝试启动这样构建的容器时出现错误,“[错误]致命错误:无法打开和锁定权限表:表存储“用户”的引擎没有此选项”(MacOS 上的 MySQL 5.7,Docker 2.1.0.5)
    【解决方案2】:

    这不能完全按照您想要的方式完成,至少在基于官方mysql 图像时,因为您需要与服务器通信以导入数据并且服务器没有运行和初始化(来自mysql的docker-entrypoint.sh) 直到容器运行,也就是镜像已经构建完成。

    不太干净的方法是在容器中运行进程,使用 mysql 映像中的 /entrypoint.sh 脚本,但您还必须注意入口点所需的所有设置(如 $MYSQL_ROOT_PASSWORD)作为一种在导入数据后立即停止守护进程的干净方法。比如:

    FROM mysql:5.6
    
    ADD data.sql /docker-entrypoint-initdb.d/00-import-data.sql
    ENV MYSQL_ROOT_PASSWORD somepassword
    ENV MYSQL_DATABASE db1
    RUN /entrypoint.sh mysqld & sleep 30 && killall mysqld
    

    是一种导致预初始化数据库的骇人听闻的方式,但是......它不起作用。原因是 /var/lib/mysql 在 mysql 的 Dockerfile 中被声明为一个卷,并且在构建过程中对该目录的任何更改在构建步骤完成后都会丢失。这可以在以下 Dockerfile 中观察到:

    FROM mysql:5.6
    
    RUN touch /var/lib/mysql/some-file && ls /var/lib/mysql
    RUN touch /var/lib/mysql/some-file2 && ls /var/lib/mysql
    

    所以我建议您使用docker commit 您描述的方式。最终结果与您想要实现的结果相同,可能除了第 2 层。

    更新:正如 OP 在下面评论的那样,提交也不包含卷。因此,唯一的方法似乎是编辑 MySQL Dockerfile 并删除 VOLUME 以将数据保留在容器内,或者将卷与容器分开管理。

    【讨论】:

    • 您好!我很抱歉迟到的答案!提交方法没有问题吗?在官方 Dockerfile 中定义了一个卷('VOLUME /var/lib/mysql'),这就是数据库所在的位置。因此,提交不会将这些文件包含在新映像中(我使用 'docker diff ' 进行了检查)。而且我不能“取消卷”安装可以吗?
    • 你是对的;这是卷对任何提交都不透明的一般问题(因此它在构建期间不起作用)。见github.com/docker/docker/issues/6999。人们通常克隆原始存储库并在自定义修改后移动 VOLUME,或者将其全部删除——参见github.com/stuartpb/rethinkdb-dockerfiles/issues/14
    【解决方案3】:

    基于 MegaWubs 的回答,我发现这个 Dockerfile 就足够了。

    FROM mysql:5.6
    RUN sed -i 's|/var/lib/mysql|/var/lib/mysql2|g' /etc/mysql/my.cnf
    

    【讨论】:

    • 使用mysql:5.7,我用的是:RUN sed -i 's|/var/lib/mysql|/var/lib/mysql2|g' /etc/mysql/mysql.conf.d/mysqld.cnf
    • 我认为他们可能也更新了 mysql:5.6 中的位置。我正在使用 mysql:5.6 并且必须使用 Chomeh 推荐的路径。
    【解决方案4】:

    MegaWubs 的回答很棒,除了这个“sleep 30”会迫使你猜测 initdb 的执行时间。 为了避免这种情况,我在 /docker-entrypoint-initdb.d 中放置了一个小 shell 脚本来执行:

    /docker-entrypoint-initdb.d/
      |- 01_my_data1.sql
      |- 02_my_data2.sql
      ...
      |- 99_last_processed_file.sh
    

    99_last_processed_file.sh

    #!/usr/bin/env bash
    touch /tmp/server_can_shutdown.txt
    

    --

    同时,在 Dockerfile 中,我运行另一个脚本来代替 Mortenn 的“sleep && killall”:

    # Dockerfile
    # ...
    COPY wait_then_shutdown.sh /tmp/wait_then_shutdown.sh
    RUN /entrypoint.sh mysqld & /tmp/wait_then_shutdown.sh  # <-- 
    RUN rm /docker-entrypoint-initdb.d/*
    

    wait_then_shutdown.sh

    #!/usr/bin/env bash
    while [ ! -f /tmp/server_can_shutdown.txt ] # <-- created by 99_last_processed_file.sh
    do
      sleep 2
    done
    kill $(pidof mysqld)
    

    --

    现在,mysqld 仅在 /docker-entrypoint-initdb.d 中处理所有其他文件时停止

    【讨论】:

    • 我发现 mysqld 没有优雅地处理 SIGTERM,所以当容器运行时它抱怨没有正常关闭并运行崩溃恢复。我可以通过将kill $(pidof mysqld) 替换为mysqladmin shutdown 来避免这种情况,但是如果在创建server_can_shutdown 文件后立即完成此操作,它会在mysqld 在init 进程之后启动时中断。所以我回来添加另一个sleep 30,就像 MegaWub 的原始脚本一样。有没有更好的办法?
    【解决方案5】:

    从 Docker 17.05 开始,可以使用多阶段构建功能来删除卷

    # Dockerfile
    FROM mysql as orig
    
    FROM ubuntu:bionic as image
    # care must be taken, this will not preserve fs ownership
    COPY --from=orig / / # this will copy all files, without metadata.
    
    ENV ... # include all commands that are not file related
    
    ENTRYPOINT ["docker-entrypoint.sh"]
    
    EXPOSE 3306
    CMD ["mysqld"]
    

    然后按照 Phil Sabaty 的回答添加您的数据。

    【讨论】:

      【解决方案6】:

      所以我对这个问题的解决方案不是对所有内容进行分层,而是创建一个基础映像并使用 --volumes-from 从纯数据容器中注入数据库文件。

      【讨论】:

      • 您能提供更多信息吗?我也想做。
      • @Morten Green 请花时间分享您的工作,我们在这里互相帮助。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-11-15
      • 1970-01-01
      • 2020-05-20
      • 1970-01-01
      • 2018-06-27
      • 2019-12-15
      • 1970-01-01
      相关资源
      最近更新 更多