首页 / 资讯中心 / 文章详情

Docker部署MySQL从入门到实战:数据卷、端口映射与权限排查

Docker部署MySQL从入门到实战:数据卷、端口映射与权限排查 ★ FEATURED ARTICLE
1. 开始动手前先想清楚容器不是虚拟机有人一上来就在 Linux 服务器上执行docker run mysql:8.0跑起来之后发现一切正常然后第二天满怀期待地去查数据结果容器被删了数据也没了。这种情况我见过太多次。Docker 和虚拟机最大的区别就是容器本身是“一次性”的镜像只是静态模板容器只是在模板上运行的一层读写环境。容器删了这一层里写的东西也跟着没了除非你用数据卷把内容留在宿主机上。另外一个容易绕晕的点是“端口映射”。MySQL 默认监听 3306 端口这是容器内部的 3306。宿主机上的 3306 如果不做映射你在宿主机上用mysql -h 127.0.0.1 -P 3306去连永远连不上。需要理解一个核心概念容器有自己独立的网络命名空间端口映射只是在宿主机上“开了一道门”把宿主机的某个端口转发到容器内部端口。这道门开在哪里、怎么开都是由docker run -p决定的。学 Docker 下的 MySQL最好的方式不是先背命令而是先在脑子里建立一个模型镜像、容器、数据卷、端口映射、环境变量、容器内客户端工具。这篇文章就是围绕这几个词展开的。你会从拉取镜像开始逐步搞懂第一次初始化密码是怎么发生的、为什么数据能持久化、为什么要用docker exec进入容器执行 SQL以及最常见的连接和权限问题到底出在哪个环节。1.1 MySQL 官方镜像的启动逻辑官方镜像mysql:8.0并不是下载下来就能直接用它内部有一套“初始化流程”。第一次启动时如果发现数据目录/var/lib/mysql是空的就会自动执行初始化脚本创建系统表、创建 root 用户、设置密码并且处理你通过环境变量传进来的各种配置。这套流程里最常用的几个环境变量MYSQL_ROOT_PASSWORD初始化时给 root 用户设置的密码仅在数据目录为空时生效。MYSQL_DATABASE初始化时自动创建的数据库。MYSQL_USER和MYSQL_PASSWORD初始化时创建的业务账号和密码。MYSQL_ROOT_HOST允许从哪些主机用 root 连接默认不设置时容器外部通常连不上这也是很多人本地 Navicat 连不上的重要原因之一。你还需要知道容器的/docker-entrypoint-initdb.d/目录里可以放.sql、.sh脚本。第一次初始化时这些脚本会按文件名顺序自动执行适合放表结构初始化、种子数据一类的文件。后面我会演示一个更直观的用法通过docker exec进入容器再调用容器内的mysql客户端执行 SQL。1.2 选镜像版本不是越新越好官方镜像的标签非常多新手不用全看记住三个就够镜像标签特点建议mysql:5.7老项目常见兼容性好已进入维护末期新项目不建议再用mysql:8.0当前大多数教程和云厂商默认版本学习和新项目首选mysql:8.4MySQL 8.4 LTS 长期支持版生产环境如果追求稳定可以考虑选版本这件事最怕的是“照抄网上教程但版本不同行为还不一样”。比如 MySQL 5.7 默认认证插件是mysql_native_password到了 MySQL 8.0 默认变成caching_sha2_password旧客户端可能会出现认证插件不兼容的问题。文章后面我专门说这个坑。2. 拉取镜像和启动容器一步步把 MySQL 跑起来2.1 基础环境检查开始之前先在终端里确认 Docker 正常工作docker version docker info docker imagesdocker version能看到客户端和服务端版本docker info能看到数据目录、容器数量、存储驱动等全局信息docker images用来确认本地已经有哪些镜像。如果你在 Windows 或 macOS 上通常用的都是 Docker Desktop。它自带图形界面容器跑没跑一眼就能看到。Linux 上用systemctl status docker看 Docker 服务状态。确认没问题再继续。2.2 拉取 MySQL 8.0 镜像docker pull mysql:8.0拉取完以后用docker images查看docker images | grep mysql会看到类似mysql 8.0 xxxxxxxxxxxx的记录。为什么建议用官方镜像而不是第三方打包镜像因为官方镜像的 Dockerfile 是公开的版本标签语义明确底层也是常见的基础系统。网上有些“全功能”镜像看起来省事实际里面加了很多你不知道的东西镜像安全和容器安全都不好保障。学习阶段选官方镜像也是在养成一个安全习惯。2.3 用 docker run 启动 MySQL 容器下面这条命令是核心中的核心docker run -d \ --name mysql-server \ -e MYSQL_ROOT_PASSWORD你的密码 \ -p 3306:3306 \ -v mysql-data:/var/lib/mysql \ --restart unless-stopped \ mysql:8.0逐项拆解一下-d后台运行不在终端里一直挂着日志。--name mysql-server给容器起一个固定名字之后不管docker exec、docker stop都直接用这个名字不用记一串容器 ID。-e MYSQL_ROOT_PASSWORD你的密码把密码传给容器的初始化脚本。注意它只在第一次创建数据目录时生效后面改密码不能靠改这个环境变量实现。-p 3306:3306把宿主机 3306 端口映射到容器 3306 端口。左边是宿主机端口右边是容器端口。如果宿主机 3306 已经被别的程序占了可以把左边改成3307外部客户端用 3307 连接。-v mysql-data:/var/lib/mysql创建名为mysql-data的命名数据卷并挂载到容器内的 MySQL 数据目录。只要这个卷还在删容器、重建容器数据都不会丢。--restart unless-stoppedDocker 服务重启或主机重启时自动把容器拉起来。这个参数对实际使用特别重要不加的话服务器重启后你得手动去启动容器。我自己一开始踩过的坑就是漏了-v。容器删掉以后所有数据库全没了只能对着空空如也的卷发呆。后来养成的习惯是先创建命名卷再跑docker run。万一要把容器彻底删了重来数据还在重启一个新的容器挂同一个卷就行。2.4 查看启动日志和容器状态docker ps docker logs mysql-serverdocker ps看容器状态STATUS 如果是Up说明还在跑。docker logs mysql-server看日志第一次启动时能看到初始化信息。等日志里出现类似ready for connections的提示就说明 MySQL 真正就绪了。如果容器启动失败日志会直接告诉你原因。哪怕你一开始看不懂 MySQL 内部报错至少也能看到是权限、端口还是磁盘问题这样排查方向就对了。有一个非常常见的坑容器启动成功后日志里显示初始化完成但过一会儿容器停了。多数原因是数据目录权限不对或者宿主机端口被占用。前者我后面专门用一节的篇幅讲。2.5 数据卷和 bind mount 怎么选Docker 提供两种常见挂载方式方式写法示例适合场景命名卷-v mysql-data:/var/lib/mysql最推荐Docker 自动管理路径权限问题少绑定挂载-v /home/user/mysql-data:/var/lib/mysql需要直接查看宿主机文件或者已经有现成数据如果你是在 Linux 上使用绑定挂载要注意 MySQL 容器内部以mysql用户运行这个用户的 UID 通常是999。你创建的目录如果默认属于 rootMySQL 可能根本没权限写数据。解决办法是给目录授权sudo chown -R 999:999 /home/user/mysql-data用命名卷就不会遇到这种权限问题所以我建议新手首选命名卷。等以后有经验了再根据自己的习惯选择 bind mount。3. 配置 MySQL字符集、排序规则和认证方式3.1 通过启动参数指定字符集数据库出现乱码绝大多数都是字符集设置问题。MySQL 8.0 默认字符集其实已经是utf8mb4但我还是建议在启动参数里显式指定避免不同的版本、不同的初始化行为带来意外。docker run -d \ --name mysql-server \ -e MYSQL_ROOT_PASSWORD你的密码 \ -p 3306:3306 \ -v mysql-data:/var/lib/mysql \ mysql:8.0 \ --character-set-serverutf8mb4 \ --collation-serverutf8mb4_unicode_ci这里的关键是--character-set-server和--collation-server是mysqld的参数不是docker run的参数。把它们放在镜像名后边Docker 启动容器时会把它们传给 MySQL 的启动入口。为什么选utf8mb4因为它能完整支持中文、表情符号而老的utf8在 MySQL 里实际上只能存一部分 Unicode 字符。排序规则选utf8mb4_unicode_ci比较通用对大多数中文项目都够用。3.2 挂载自定义 my.cnf命令行传参数虽然方便但配置一多就不合适了。更常见的做法是准备一个自定义配置文件挂载到容器里。先在宿主机创建一个配置文件mkdir -p ~/docker/mysql/conf cat EOF ~/docker/mysql/conf/mysql-custom.cnf [mysqld] character-set-server utf8mb4 collation-server utf8mb4_unicode_ci max_connections 200 EOF然后启动容器时挂载到/etc/mysql/conf.d/目录下docker run -d \ --name mysql-server \ -e MYSQL_ROOT_PASSWORD你的密码 \ -p 3306:3306 \ -v mysql-data:/var/lib/mysql \ -v ~/docker/mysql/conf/mysql-custom.cnf:/etc/mysql/conf.d/mysql-custom.cnf:ro \ mysql:8.0注意官方镜像的/etc/mysql/my.cnf最后会 include/etc/mysql/conf.d/下所有.cnf文件。把自定义配置放在这个目录里不会覆盖原始配置只做追加。挂载时我特意加了:ro容器内只读避免被误改。改完配置文件后需要重启容器docker restart mysql-server然后验证配置是否生效docker exec mysql-server mysql -uroot -p你的密码 -e SHOW VARIABLES LIKE character%;如果你看到character_set_server的值是utf8mb4就说明配置生效了。3.3 MySQL 8.0 的认证插件问题MySQL 8.0 默认的认证插件是caching_sha2_password加密强度更高。但这会带来一个兼容性问题一些老版本的客户端、旧版语言驱动不认识这个插件连接时报错Authentication plugin caching_sha2_password cannot be loaded遇到这种情况第一选择应该是升级客户端工具。MySQL 8.0 发布已经很多年了新版本工具都支持这个插件。万一你没法升级客户端退而求其次的做法是在 MySQL 里创建一个使用旧认证方式的账号CREATE USER app% IDENTIFIED WITH mysql_native_password BY App123; GRANT ALL PRIVILEGES ON app_db.* TO app%; FLUSH PRIVILEGES;或者直接在容器启动参数里加上--default-authentication-pluginmysql_native_password但要清楚mysql_native_password的加密方式和安全性都比caching_sha2_password弱。只能作为过渡手段不要长期依赖。4. 进入容器执行 SQL交互式与非交互式4.1 交互式进入容器打开 mysql 客户端容器跑起来之后最直接的 SQL 执行方式就是进入到容器内部使用容器里自带的mysql客户端。docker exec -it mysql-server bash进入 bash 后再执行mysql -uroot -p输入密码就能看到mysql提示符。接下来像在本地操作 MySQL 一样执行 SQL 就行SHOW DATABASES; USE mysql; SELECT user, host, plugin FROM user;这里docker exec -it里的-i表示保持标准输入打开-t表示分配一个伪终端。少了-t命令能执行但不会有交互式排版少了-i你可能没法正常输入内容和回车。所以查询、登录这种需要用户输入的交互场景-it最好一起用。4.2 非交互式直接执行 SQL在自动化场景里你不可能每次都手动登录再敲 SQL。可以用-e参数直接在容器里执行 SQLdocker exec mysql-server mysql -uroot -p你的密码 -e SHOW DATABASES; SELECT NOW();输出结果会直接打印在终端里。如果你要执行一个 SQL 文件可以先把文件放到宿主机然后通过 stdin 传入docker exec -i mysql-server mysql -uroot -p你的密码 app_db init.sql这里-i是必须的因为重定向把宿主机文件内容作为标准输入传给了容器内进程。不要写成docker exec -it因为不需要分配伪终端反而可能引发奇怪的换行问题。如果你觉得密码直接出现在命令行里不安全还可以这样docker exec -e MYSQL_PWD你的密码 mysql-server mysql -uroot -e SELECT 1MYSQL_PWD是读取的 MySQL 客户端环境变量能避免在命令行参数里暴露密码。不过docker exec -e传递的变量会出现在容器进程环境里也谈不上绝对安全。真正在生产环境做自动化更稳妥的方式是使用 MySQL 的--defaults-extra-file配置临时文件并限制文件权限这里不展开说了。4.3 在宿主机上直接连接容器如果你用的是 Navicat、DBeaver、MySQL Workbench 这类可视化工具不需要先进容器直接连宿主机映射端口就行mysql -h 127.0.0.1 -P 3306 -uroot -p这里的-h 127.0.0.1指向宿主机-P 3306指向映射出来的端口。注意-P是大写-p是小写一个是端口一个是密码。这个问题看着低级但我真见过有人因为大小写写错白白折腾了半小时。5. 外部客户端连接与权限排查5.1 用 DBeaver、Navicat 连接容器连接参数基本固定参数值主机127.0.0.1或localhost端口3306如果宿主机端口映射成 3307 就写 3307用户名root密码初始化时MYSQL_ROOT_PASSWORD设的密码这里有个细节很多人把“容器里的 3306”和“宿主机连接端口”弄混。docker run -p 3307:3306之后数据库实际监听的是容器内 3306但外部客户端要填 3307。填 3306 反而连不上。5.2 为什么 root 连接总是被拒绝在官方 MySQL 镜像里root 账号默认只允许本地连接。所谓“本地”指的是容器内部。你从宿主机用 Navicat 连接时MySQL 看到的是来自一个陌生 IP 的连接自然拒绝。解决方法有两种。第一种是我们在启动容器时直接设置 root 允许所有主机docker run -d \ --name mysql-server \ -e MYSQL_ROOT_PASSWORD你的密码 \ -e MYSQL_ROOT_HOST% \ -p 3306:3306 \ -v mysql-data:/var/lib/mysql \ mysql:8.0%是 MySQL 里的通配符表示允许从任何主机连接。适合个人开发环境。如果是公司内网环境建议把%换成具体网段比如192.168.1.%降低暴露风险。第二种是创建专门的应用账号CREATE USER app% IDENTIFIED BY App123; GRANT ALL PRIVILEGES ON app_db.* TO app%; FLUSH PRIVILEGES;这样你就不需要把 root 暴露给外部业务服务一律用 app 账号连接。我对这个做法的评价是简单、省事而且更符合最小权限原则。5.3 MySQL SSL 连接错误另一个高频问题就是 SSL。MySQL 8.0 默认启用 SSL 相关配置新客户端一般能自动适配但旧驱动或者连接工具版本太老时会报类似SSL connection error: error:1425F102 ...解决思路分两步优先升级客户端工具或者数据库驱动版本。如果只是本地开发环境保证网络可信的前提下可以在连接参数里关闭 SSL。以 mysql 自带的命令行客户端为例mysql -h 127.0.0.1 -P 3306 -uroot -p --ssl-modeDISABLEDDBeaver 的连接设置里也有 SSL 配置页选“不使用 SSL”即可。生产环境不要随便关这是连接层面的最后一道防线能开就开。6. 常见问题与排查实录我在用 Docker 跑 MySQL 的过程中踩过不少别人也大概率会踩的坑。整理成一张速查表语言不绕弯直接给原因和解法现象可能原因解决方案容器启动失败端口报错bind: address already in use宿主机 3306 端口被其他进程占用换宿主机端口如-p 3307:3306容器起来了但外部客户端连不上未设置MYSQL_ROOT_HOSTroot 默认只允许容器内连接启动时加-e MYSQL_ROOT_HOST%或创建远程账号连接时报Access denied for user root...账号 host 权限不匹配或密码错误用docker exec进入容器检查账号 host 和认证方式报认证插件caching_sha2_password无法加载客户端版本过旧升级客户端或创建mysql_native_password用户临时过渡日志里提示Cant create/write to filebind mount 目录权限不对sudo chown -R 999:999 数据目录中文乱码字符集或客户端连接字符集没统一服务端配置utf8mb4连接串也指定utf8mb4删掉容器重新创建后修改环境变量密码不生效数据卷已初始化root 密码不会因为环境变量变化而重置正确的做法是备份后使用 SQL 修改密码或做密码恢复6.1 端口占用的处理宿主机上如果已经装了 MySQL或者另外的容器已经在用 3306再启动一个映射到 3306 的容器一定会报错。这种时候不需要纠结把宿主机端口换掉就行docker run -d \ --name mysql-server \ -p 3307:3306 \ ...容器内部仍然是 3306外部连接改成-P 3307。修改端口只会影响外部访问容器里的 MySQL 客户端仍然通过内部 socket 连接不受影响。6.2 bind mount 权限问题用-v /home/user/mysql-data:/var/lib/mysql这种方式挂载数据目录在 Linux 上经常遇到权限问题。原因在于容器内的 MySQL 进程不是以 root 运行而是以mysql用户运行。宿主机上你这个目录原来属于 rootMySQL 无法写入。解决办法sudo chown -R 999:999 /home/user/mysql-data如果部署环境开启了 SELinux还可能遇到更隐蔽的权限拒绝需要在挂载参数里加上:z之类的标签。第一次遇到这种问题不要慌先看docker logs mysql-server日志里会明确写到哪个路径、哪个权限出错。顺着路径走基本都能解决。6.3 忘记 root 密码怎么办这个场景让人头大但先说一个误区不要以为重新docker run一个新的容器配一个新的环境变量就能把密码改掉。只要你的数据卷不是空的MySQL 初始化脚本就会跳过初始化过程MYSQL_ROOT_PASSWORD不会再起作用。正确的恢复思路是在容器启动命令里临时加上--skip-grant-tables绕过权限表然后进入容器把密码改回来。实际操作起来流程比较复杂新手如果操作失误可能把权限表搞坏所以我更建议提前做好备份不要等到忘记密码再救。如果数据不重要最简单粗暴的方式就是把旧卷备份后删掉重新初始化一个全新容器。6.4 用 docker logs 做第一排查工具无论遇到什么问题第一步永远是docker logs mysql-server日志会告诉我是端口冲突还是数据目录权限还是初始化 SQL 脚本语法错误还是内存不足。不要一上来就改配置、删容器先看日志。日志里没有有效信息的时候再考虑进入容器排查环境。7. 日常维护备份、恢复和容器管理习惯7.1 用 mysqldump 做备份备份这件事再强调都不为过。Docker 里的数据库备份逻辑其实就是把宿主机上的备份请求放进容器让容器内的mysqldump执行然后把标准输出重定向到宿主机文件。单库备份docker exec mysql-server sh -c exec mysqldump -uroot -p$MYSQL_ROOT_PASSWORD --single-transaction --routines --triggers app_db app_db_$(date %F).sql这里的关键是sh -c因为我需要在容器内展开$MYSQL_ROOT_PASSWORD这个环境变量。--single-transaction适合 InnoDB备份过程中不会锁表对线上影响小。--routines和--triggers会带上存储过程和触发器。恢复备份docker exec -i mysql-server sh -c exec mysql -uroot -p$MYSQL_ROOT_PASSWORD app_db app_db_$(date %F).sql注意恢复时用的是-i而不是-it因为文件重定向进来的是标准输入流不需要分配交互式终端。7.2 容器启动策略和资源限制生产服务器重启后MySQL 容器能不能自动恢复取决于--restart参数。我习惯用unless-stopped而不是always。区别在于如果某次手动docker stop了容器unless-stopped不会在 Docker 重启后自作主张把它拉起来always不管你为什么停只要 Docker 启动就会拉起来。对于数据库这类需要人工判断的组件unless-stopped更合理。已经启动的容器也能改策略docker update --restart unless-stopped mysql-server另外在服务器上跑 MySQL 容器最好限制资源避免它把宿主机内存吃满docker run -d \ --name mysql-server \ --memory512m \ --cpus1 \ ...--memory512m限制容器最多使用 512MB 内存--cpus1限制使用 1 个 CPU 核心。具体数值看业务量我这里给的只是学习环境的参考。生产环境不要拍脑袋定要看实际监控数据。7.3 把启动命令固化下来最后再说一个我自己的使用习惯。我刚开始学 Docker 的时候每创建一个容器都靠手敲一条长长的docker run。结果过两天要重建容器参数记不全又翻历史记录还容易漏掉配置。后来我的做法是每个项目建一个目录把docker run命令写成一个start.sh脚本配置文件和初始化 SQL 也放在同一个目录下。重建容器时直接执行脚本参数不会丢别人接手你的环境也能一眼看懂你当初是怎么启动的。比如#!/bin/bash docker run -d \ --name mysql-server \ -e MYSQL_ROOT_PASSWORD你的密码 \ -e MYSQL_ROOT_HOST% \ -p 3306:3306 \ -v mysql-data:/var/lib/mysql \ -v ~/docker/mysql/conf/mysql-custom.cnf:/etc/mysql/conf.d/mysql-custom.cnf:ro \ --restart unless-stopped \ mysql:8.0这个脚本给自己用也给团队用比口头交代“我就是这么启动的”要靠谱得多。最后再说一句Docker 管理 MySQL 本身不复杂复杂的是把数据持久化、权限控制、备份恢复、连接兼容性这些底层问题想清楚。把这篇文章里的关键命令亲手敲一遍再故意删掉容器重建一次体验一下数据还在的感觉你对容器和数据卷的理解就会完全不同。
阅读完成 · 觉得有帮助?
咨询建站