在Docker中持久化MySQL数据

8

我是Docker的新手。我已经成功创建了一个Docker镜像,其Dockerfile如下:

From alpine:3.4
MAINTAINER SomeName - domain.tld

# Timezone
ENV TIMEZONE Asia/Kolkata

# RUN sed -i 's#dl-cdn\.alpinelinux\.org#mirrors\.aliyun\.com#' /etc/apk/repositories

# install mysql, apache and php and php extensions, tzdata, wget
RUN echo "@community http://dl-cdn.alpinelinux.org/alpine/edge/community" >> /etc/apk/repositories && \
    apk add --update \
    mysql mysql-client \
    apache2 \
    curl wget \
    tzdata \
    php5-apache2 \
    php5-cli \
    php5-phar \
    php5-zlib \
    php5-zip \
    php5-bz2 \
    php5-ctype \
    php5-mysqli \
    php5-mysql \
    php5-pdo_mysql \
    php5-opcache \
    php5-pdo \
    php5-json \
    php5-curl \
    php5-gd \
    php5-gmp \
    php5-mcrypt \
    php5-openssl \
    php5-dom \
    php5-xml \
    php5-iconv \
    php5-xdebug@community

RUN curl -sS https://getcomposer.org/installer | \
    php -- --install-dir=/usr/bin --filename=composer

# configure timezone, mysql, apache
RUN cp /usr/share/zoneinfo/${TIMEZONE} /etc/localtime && \
    echo "${TIMEZONE}" > /etc/timezone && \
    mkdir -p /run/mysqld && chown -R mysql:mysql /run/mysqld /var/lib/mysql && \
    mysql_install_db --user=mysql --verbose=1 --basedir=/usr --datadir=/var/lib/mysql --rpm > /dev/null && \
    mkdir -p /run/apache2 && chown -R apache:apache /run/apache2 && chown -R apache:apache /var/www/localhost/htdocs/ && \
    sed -i 's#AllowOverride none#AllowOverride All#' /etc/apache2/httpd.conf && \
    sed -i 's#ServerName www.example.com:80#\nServerName localhost:80#' /etc/apache2/httpd.conf && \
    sed -i '/skip-external-locking/a log_error = \/var\/lib\/mysql\/error.log' /etc/mysql/my.cnf && \
    sed -i '/skip-external-locking/a general_log = ON' /etc/mysql/my.cnf && \
    sed -i '/skip-external-locking/a general_log_file = \/var\/lib\/mysql\/query.log' /etc/mysql/my.cnf

# Configure xdebug
RUN echo "zend_extension=xdebug.so" > /etc/php5/conf.d/xdebug.ini && \ 
    echo -e "\n[XDEBUG]"  >> /etc/php5/conf.d/xdebug.ini && \ 
    echo "xdebug.remote_enable=1" >> /etc/php5/conf.d/xdebug.ini && \  
    echo "xdebug.remote_connect_back=1" >> /etc/php5/conf.d/xdebug.ini && \ 
    echo "xdebug.idekey=PHPSTORM" >> /etc/php5/conf.d/xdebug.ini && \ 
    echo "xdebug.remote_log=\"/tmp/xdebug.log\"" >> /etc/php5/conf.d/xdebug.ini

#start apache
RUN echo "#!/bin/sh" > /start.sh && \
    echo "httpd" >> /start.sh && \
    echo "nohup mysqld --skip-grant-tables --bind-address 0.0.0.0 --user mysql > /dev/null 2>&1 &" >> /start.sh && \
    echo "sleep 3 && mysql -uroot -e \"create database db;\"" >> /start.sh && \
    echo "tail -f /var/log/apache2/access.log" >> /start.sh && \
    chmod u+x /start.sh

WORKDIR /var/www/localhost/htdocs/

EXPOSE 80
EXPOSE 3306

#VOLUME ["/var/www/localhost/htdocs","/var/lib/mysql","/etc/mysql/"]
ENTRYPOINT ["/start.sh"]

理想情况下,我希望docker镜像包含Apache、PHP和MySQL - 都在一个镜像中。我希望这个镜像尽可能便携。因此,每次我需要启动新项目时,我只需创建一个新容器并挂载卷,然后开始编码。
当我启动一个新容器时,一切都如预期的那样。但是mysql数据无法被挂载。我认为这是因为挂载是单向的(或者不是吗?)如果我在启动新容器时添加-v /project_dir/data:/var/lib/mysql,mysql将无法启动,因为project_dir/data为空。我的问题是:
1. 如何将数据从我的project_dir挂载到容器内的/var/lib/mysql? 2. 如何为不同的容器设置不同的mysql密码?
编辑:如果我不挂载本地卷(即project_dir/data)并启动容器,获取shell tty,我看到/var/lib/mysql有几个文件,我假设这些文件是运行mysql和其初始数据库所需的。但是,如果我挂载本地卷并在容器内ls,则它是空的。这就是为什么MySQL没有启动并且我无法从容器外部持久化数据的原因。我的问题是:如何让它运行并持久化来自本地目录的数据。

2
图像应该独立于您的本地环境。您必须使用VOLUME标记目录,该目录将从外部挂载。当您在Docker中运行图像时,可以使用“-v”参数将其连接到本地环境。这样即使在Kubernetes上也可以正常工作。 - JosMac
不要在这种情况下使用Docker。Docker是一种应用程序容器技术,你正在构建一个操作系统容器/黄金镜像。请使用虚拟机,或者如果您坚持要使用容器只是为了很酷,请使用OS容器(例如OpenVZ)。您的当前方法会遇到很多问题。 - bartimar
2个回答

4

问题:

这里的问题是在 Docker 镜像构建过程中创建了一个空数据库:

mysql_install_db --user=mysql --verbose=1 --basedir=/usr --datadir=/var/lib/mysql --rpm > /dev/null && \

但是,当您从此镜像创建容器时,您会在/var/lib/mysql文件夹上挂载一个卷。这将隐藏您的容器数据,以公开主机的文件夹。因此,您会看到一个空文件夹。 解决方案
如果您查看https://hub.docker.com/_/mysql/,您会发现这个问题是通过在容器实际启动时创建数据库来解决的,而不是在创建镜像时创建。这回答了您的两个问题:
1. 如果您使用已挂载的卷启动容器,则数据库初始化将在之后执行,并且实际上将在主机的文件系统中编写数据。 2. 您必须将这些信息作为环境变量传递。
换句话说,在ENTRYPOINT中使用脚本初始化您的DB,而不是直接在镜像中进行。 警告
一些警告。您所做的镜像方式并不是真正推荐的,因为Docker的理念是“每个容器一个进程”。您在这里遇到的困难是必须在同一个容器上启动多个服务(如apache、Mysql等),因此您可能需要在entrypoint中对两者都进行处理,这很令人困惑。此外,如果一个服务失败,您的容器仍将保持运行状态,但不会按预期工作。
因此,我建议将您所做的拆分为每个进程1个镜像,然后使用原始Docker启动它们,或者使用docker-compose等工具。
此外,这将使您能够使用来自Docker Hub:https://hub.docker.com的现有且高度可配置的映像。对您来说工作量更少,出错的可能性也更小。

谢谢。这很有道理。然而,我对Docker非常陌生。您能否详细解释一下如何在ENTRYPOINT中添加脚本?ENTRYPOINT不是在Dockerfile内使用的吗?如果是这样,那么在构建镜像时运行脚本和在容器启动时运行脚本之间有什么区别呢?难道Dockerfile不仅用于构建镜像吗? - abhisek
请查看 https://github.com/docker-library/mysql/tree/10eb5eab8d3d793c49a701ed7a42e1e5d1c9bb59/8.0。这就是MySQL镜像的制作方式。再次鼓励您使用它(选择您需要的mysql版本)。现在回答您的问题:`ENTRYPOINT`是容器启动时要执行的命令。为了做到这一点,您必须在Dockerfile中定义它。因此,Dockerfile用于构建镜像,同时给出了有关创建容器时要执行的指令,例如配置和启动进程。 - Alexandre FILLATRE

-1

当您在docker容器中拥有数据库时,使用卷是唯一的选择。也许我们应该说明,“容器内部的数据库”意味着不使用卷,并且将数据存储在标准10GB(如果默认大小已被修改,则更大)中,而使用卷的数据库则意味着docker run -v myvolume...

文档docs.docker.com/engine/tutorials/dockervolumes是必看的

我完全同意Alexandre的答案,但我认为将数据库放在容器内部是错误的,还有另一个原因。默认情况下,镜像的大小(运行容器时)为10 GB。这对于某些可执行文件、库和配置文件来说应该相当舒适。您可以在创建镜像时修改此默认值并设置较大的值。但是当您达到这个新限制时会发生什么?假设您认为数据库的大小将为100 GB,并且实际上,您放置了比计划更多的数据,最终需要5或20倍?卷可以位于SAN上,并在需要时进行扩展。


你是什么意思:“将数据库放在容器内”?你的意思是不使用卷吗? - Alexandre FILLATRE
不,我的意思是使用卷是存储数据库的唯一方式。 - user2915097
好的,那么我的回答可能不够清晰,因为这也是我建议的:)。你看我能如何改进它? - Alexandre FILLATRE
@user2915097 -- 要么将该评论移动到您的答案中,要么将您的答案删除以避免混淆。 - Rick James

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