Docker MySQL最佳实践?

标题之所以加个问号是因为我也不确定这篇文章讲述的是否为最佳实践,本文所有内容均翻译自mysql-Docker Hub,Docker Hub是Docker官方提供的类似于GitHub的东西。

相对于网上各种天花烂坠的技巧,我相信官方提供的说明更接近最佳实践。

原文点这里,下面是译文:

如何使用这个镜像

运行MySQL服务实例

运行一个MySQL实例很简单:

1
$ docker run --name some-mysql -e MYSQL_ROOT_PASSWORD=my-secret-pw -d mysql:tag

其中some-mysql为你指定的容器的名称,my-secret-pw是root用户的密码,tag是指定的镜像的版本。原文页面上面有当前所有的版本号。

译注:运行命令前也可以通过docker images查看本地当前已有的镜像及标签,如果上面的命令指定的标签本地不存在,那么会自动pull一个下来。如果指定的标签远程不存在,会有报错提示。
插播一句,如果没有为root设置密码,需要先到log里找到临时密码,然后用alter user 来修改才可以。

通过MySQL命令客户端连接MySQL

下面的命令创建另一个mysql容器并且运行mysql命令来连接之前的mysql容器,可以让你对数据库实例执行sql语句:

1
$ docker run -it --network some-network --rm mysql mysql -hsome-mysql -uexample-user -p

这里的some-mysql是之前创建的mysql容器的名字(连接到some-networkDocker网络)。

译注:上面的命令-h参数用容器的名字有点怪,亲测也连不上。
Docker网络可以通过docker network ls来查看,通常是用来自己组网用的。也可以用docker network inspect bridge来查看某个网络的详细信息。
如果想查看上面的MySQL容器的ip信息,可以用这个命令docker inspect some-mysql,在输出中能看到ip和network等信息。
--rm可以理解为用后即焚,exit之后容器会被自动删除。
这个命令会pull一个标签为latest的镜像到本地,因为之前已经pull过mysql5.7,所以可以用如下命令替代:docker run -it --rm mysql:5.7 mysql -hsome.mysql.host -usome-mysql-user -p

这个镜像也可以用作非Docker环境或者远程数据库实例的客户端:

1
$ docker run -it --rm mysql mysql -hsome.mysql.host -usome-mysql-user -p

更详细的MySQL客户端命令可以查看MySQL文档

通过docker stack deploydocker-compose

一个mysqlstack.yml例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Use root/example as user/password credentials
version: '3.1'

services:

db:
image: mysql
command: --default-authentication-plugin=mysql_native_password
restart: always
environment:
MYSQL_ROOT_PASSWORD: example

adminer:
image: adminer
restart: always
ports:
- 8080:8080

运行docker stack deploy -c stack.yml mysql或者docker-compose -f stack.yml up,等待初始化完成,可以通过http://swarm-ip:8080http://localhost:8080,或者http://host-ip:8080来访问。

译注:docker stack与docker-compose都是跟集群相关的命令,没有真正使用过,不太了解。
上面的配置文件定义了两个容器,一个是mysql,一个是adminer,标签都是latest
首次运行还需先执行docker swarm init命令来初始化集群环境。然后访问后面的链接会出现类似的管理界面,也即adminer的界面:

通过shell访问容器;查看MySQL日志

docker exec命令可以允许你在Docker容器内部运行命令,下面的命令会提供一个mysql容器内部的bash shell:

1
$ docker exec -it some-mysql bash

日志可以通过Docker的容器日志来访问:

1
$ docker logs some-mysql

使用自定义MySQL配置文件

默认的MySQL配置文件在/etc/mysq/my.cnf,可能还用!includedir包含额外的目录,例如/etc/mysql/conf.d。具体文件和目录需要查看mysql镜像来获取详细信息。

如果客户端配置文件是/my/custom/config-file.cnf,你可以运行mysql容器像下面这样(注:命令中仅使用了配置文件的目录):

1
$ docker run --name some-mysql -v /my/custom:/etc/mysql/conf.d -e MYSQL_ROOT_PASSWORD=my-secret-pw -d mysql:tag

这个命令将会创建一个新的容器some-mysql,这个MySQL实例使用/etc/mysql/my.cnf/etc/mysql/conf.d/config-file.cnf两个文件的组合进行配置,后者优先

译注:后者优先代表会覆盖源目录。开始用的时候还以为容器内的文件会自动同步到主机上,结果不行,需要自己创建。。。

cnf文件的配置

一些配置选项可以作为参数传给mysqld。这可以让你在没有配置文件的情况下灵活的自定义容器。例如,如果你想改变所有的表的默认编码和字符集为UTF-8(utf8mb4),运行如下命令即可:

1
$ docker run --name some-mysql -e MYSQL_ROOT_PASSWORD=my-secret-pw -d mysql:tag --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci

通过下面的命令查看完整的变量列表:

1
$ docker run -it --rm mysql:tag --verbose --help

译注:个人感觉还是有配置文件比较方便,否则如果配置有修改还得删了容器,再重新创建。也即容器不能同名,强迫症患者表示不能忍。
而且在使用一段时间后也可能碰到需要调配置的情况,总不能也删了重建。

环境变量

当你启动mysql镜像的时候,可以通过在docker run命令中设置一个或多个环境变量来调整MySQL实例的配置。注意如果你开启一个包含数据库的容器,那么设置的任何变量都不会产生影响:任何已经存在的数据库在容器启动的时候都保持不变。

可以查看MySQL本身关于环境变量的文档。

MYSQL_ROOT_PASSWORD

这个变量是强制的,用来指定root用户的密码,在上面的例子里,设置为了my-secret-pw

MYSQL_DATABASE

这个变量是可选的,允许你在镜像启动的时候指定被创建的数据库名字。如果user/password被应用,这个user将被授权为该数据库的超级管理员用户。

MYSQL_USER, MYSQL_PASSWORD

这些变量是可选的,用来设置新用户的用户名和密码。这个用户会被授权为MYSQL_DATABASE数据库的超级管理员用户。这两个变量在创建用户的时候是必须的。

不需要使用这个机制来为root用户创建密码,给root用户指定密码用MYSQL_ROOT_PASSWORD变量。

MYSQL_ALLOW_EMPTY_PASSWORD

可选参数,设置为yes允许root用户使用空密码登录。注意这种做法是不推荐的,除非你知道在做什么,这将使MySQL实例完全不受保护,任何人都有权限。

MYSQL_RANDOM_ROOT_PASSWORD

可选参数,设置为yes会初始化一个随机密码给root用户。初始化的root密码将会打印到标准输出(GENERATED ROOT PASSWORD: …..)。

译注:自测并没有打印在标准输出,而是在log里面。

MYSQL_ONETIME_PASSWORD

初始化完成后,将root用户的密码设置为过期(不是MYSQL_USER指定的用户),在首次登录的时候强制修改密码。此功能仅使用于MySQL 5.6+。在MySQL 5.5上使用会抛出错误。

译注:最开始用docker的时候,的确遇到了这种情况,但是写这篇博客的时候做了测试,并没有出现需要重设密码的情况。而且翻了MySQL的文档,它里面说了这个选项默认就是开启的,除非配置了MYSQL_ROOT_PASSWORDMYSQL_ALLOW_EMPTY_PASSWORD,而且这个选项必须结合MYSQL_RANDOM_ROOT_PASSWORD选项才可以(文档下面也有说明,不加docker启动不起来),但是每次仍然可以用随机密码进行访问。

Docker安全

之前的命令都将敏感信息放在命令上,一种替换方案是在之前的变量后面添加_FILE,初始化的时候会在容器内对应的文件上加载变量值。特别是这个选项用在加载密码的时候,例如密码存储在/run/secrets/<secret_name>

1
$ docker run --name some-mysql -e MYSQL_ROOT_PASSWORD_FILE=/run/secrets/mysql-root -d mysql:tag

当前这个选项仅支持MYSQL_ROOT_PASSWORD, MYSQL_ROOT_HOST, MYSQL_DATABASE, MYSQL_USER, MYSQL_PASSWORD

译注:这里的文件是容器内的文件,但是容器还没创建呢,也没法进入容器,怎么配置密码?
我用的方法是添加-v参数,从本地挂载目录到容器上,然后再指定文件,不过这样做貌似对安全性没什么提升,因为文件目录暴漏了。
所以最好还是修改密码。

初始化新鲜的实例

当容器在第一次开启的时候,会根据配置变量提供的名字创建和初始化数据库。此外,还将执行容器目录/docker-entrypoint-initdb.d下的所有的以.sh.sql.sql.gz后缀的文件。文件将会按字母顺序执行。你可以很容易的挂载sql dump文件到该目录,并且使用这些数据发布一个自定义镜像。sql文件默认会被导入到MYSQL_DATABASE变量指定的数据库里。

译注:类似这样的命令docker run --name mysql -v /root/initdb:/docker-entrypoint-initdb.d -e MYSQL_ROOT_PASSWORD=123456 -e MYSQL_DATABASE=test -d mysql:5.7
感觉是先创建一个容器,然后再发布为新的镜像,这样会内置一些数据?

注意事项

数据存储在哪

重要说明:运行在Docker容器上的应用有几种存储数据的方式,我们鼓励MySQL用户去熟悉下面的选项:

  • 让Docker使用自己内部的卷管理方案将数据写在主机上。这是默认的方式,简单透明。缺点是很难在主机上通过应该程序定位数据文件。

  • 在主机上创建一个数据目录,并且挂在到容器内。这会将数据放置在主机上已知的位置并且很容易访问这些文件。缺点是用户需要确保目录存在,并且目录权限和其他安全机制设置正确。

通过Docker文档来了解不同存储方式及变体是个好的方法,并且有很多博客和论坛提供建议。下面展示上面提到的第二种方法的流程:

  1. 在主机上创建目录,例如/my/own/datadir

  2. 用如下命令启动mysql容器:

    1
    $ docker run --name some-mysql -v /my/own/datadir:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=my-secret-pw -d mysql:tag

-v /my/own/datadir:/var/lib/mysql部分挂载主机上的/my/own/datadir作为容器内部的/var/lib/mysql,MySQL默认会写数据到这里。

译注:我最开始用的第二种方法,因为我以为第一种方法会将数据耦合在新发布的镜像内,现在看了可能也不是。

MySQL初始化完成前不能连接

当容器启动后数据库还没初始化,随后默认的数据库被创建。这是默认的行为,也意味着在容器完全初始化完成前不能接收连接。这可能在使用自动化工具的时候导致问题,例如docker-compose,他同时启动几个容器。

如果你尝试连接的MySQL应用无法处理MySQL停机或者等待MySQL完全启动的情况,则需要在在服务启动前来循环重连。参考官方镜像的实现,WordPress或者Bonita

在已有数据库下使用

如果mysql容器实例启动时挂载了包含数据的目录,$MYSQL_ROOT_PASSWORD变量会被忽略,之前存在的数据不会有任何改变。

用指定用户运行

如果你知道已经配置合适的数据库的目录权限,或者你需要用指定的UID/GID运行mysqld(root是0),可以使用--user选项来设置任何值,已实现访问和配置:

1
2
3
4
$ mkdir data
$ ls -lnd data
drwxr-xr-x 2 1000 1000 4096 Aug 27 15:54 data
$ docker run -v "$PWD/data":/var/lib/mysql --user 1000:1000 --name some-mysql -e MYSQL_ROOT_PASSWORD=my-secret-pw -d mysql:tag

数据导出

大多数数据库工具都能使用,尽管某些情况下使用起来比较复杂,以确保能连接mysqld服务。一个简单的方式是的同一个容器上使用docker exec

1
$ docker exec some-mysql sh -c 'exec mysqldump --all-databases -uroot -p"$MYSQL_ROOT_PASSWORD"' > /some/path/on/your/host/all-databases.sql

从dump文件导入

使用docker exec添加-i标志,例如:

1
$ docker exec -i some-mysql sh -c 'exec mysql -uroot -p"$MYSQL_ROOT_PASSWORD"' < /some/path/on/your/host/all-databases.sql

(完)