☰
手把手教你用Dockerfile构建MySQL 8.4自定义镜像
2026/10/6 16:45:54 网站建设 项目流程

说实话,用Dockerfile自己构建MySQL镜像这件事,听起来像是“官方镜像都现成的,何必重复造轮子”,但真做过的朋友都知道,官方镜像在某些场景下就是不够用。比如公司要求统一走内部镜像仓库,比如CentOS环境里需要特定glibc版本兜底,比如你希望镜像里默认就带上初始化脚本和自定义配置,再比如你的应用需要一个内置了指定插件和参数的MySQL 8.4环境——这些时候,手写Dockerfile就是刚需。

这篇文章就以“基于Ubuntu/CentOS基础镜像,手动构建MySQL 8.4自定义镜像”为主线,把Dockerfile的设计思路、关键指令、初始化机制和坑点一次说清楚。我会贴出可以直接抄作业的完整Dockerfile和构建指令,也会把为什么这样写、为什么踩坑的底层逻辑掰开揉碎讲。适合刚接触容器化但已经能看懂Dockerfile基本语法的读者,也适合想摆脱官方镜像束缚、自己做基础镜像运维的同行。

1. 这次折腾的基本盘:为什么要自己构建MySQL 8.4镜像

1.1 官方镜像确实香,但自定义镜像解决的是“最后一公里”

mysql/mysql-server:8.4这类官方镜像本身质量很高,开箱即用。它的默认配置、目录结构、入口脚本都经过大量验证,Pull下来就能跑。可实际情况往往是:我要的不是“一个能跑的MySQL”,而是“一个符合我团队规范的MySQL”。

举个例子,官方镜像默认的mysqld参数很多是“能用但性能平庸”的取值,生产环境肯定要调innodb_buffer_pool_size、max_connections。这些参数你可以在运行时挂载配置文件进去,但如果是几十台机器、成百上千个环境,通过-v挂载配置文件的方式就显得很散。把配置直接烤进镜像,或者通过环境变量驱动启动脚本去生成配置,这才是镜像该有的“自包含”姿态。

另一个常见诉求是基础镜像的统一管理。很多企业现在都要求基础镜像必须统一,全用Ubuntu就全用Ubuntu,全用CentOS就全用CentOS。这时候你直接拿官方MySQL镜像,它底层是什么发行版、什么版本、带什么依赖,你无法把控。自己构建,从基础镜像开始就是可控的。

1.2 Ubuntu路线和CentOS路线的选择逻辑

标题把Ubuntu和CentOS都点了出来,不是没有原因的。这两条路线的差异比想象中大,主要集中在几个地方:

包管理器的差异直接影响Dockerfile写法。Ubuntu用apt,CentOS用yum,安装MySQL官方仓库的步骤完全两套。这不光是命令不同,还涉及仓库配置文件的路径、GPG密钥的导入方式、依赖包名称的差异。比如Ubuntu上安装mysql-community-server会自动拉起libaio1等依赖,CentOS 7上可能还需要手动补libaio和numactl-libs。

glibc版本的差异也很关键。CentOS 7的glibc是2.17,Ubuntu 22.04的glibc是2.35。MySQL 8.4的官方二进制包对glibc版本有要求,更老的CentOS 7在安装新版MySQL时偶尔会遇到“版本过旧”的警告或运行时的莫名崩溃,这就是glibc说事。所以选CentOS路线,我一般建议直接用CentOS 7的官方仓库包,不要自己去编译源码,否则会被依赖问题折腾到怀疑人生。

维护状态的差异是2025年绕不开的现实。CentOS 7的生命周期已经走到了尽头,各软件源也在逐步清退旧包。如果新项目还在坚持CentOS 7,我会诚实地劝一句:尽量迁移到Rocky Linux或AlmaLinux。本文里CentOS路线的Dockerfile我依然给出,因为存量场景确实还有,但你的新环境建议看Ubuntu路线,或者把基础镜像换成Rocky Linux 9。

1.3 MySQL 8.4和之前版本的关键差异,Docker化时要知道

MySQL 8.4是LTS版本,注意,它不是8.0的简单升级,而是“当前创新版精简后再固化”的产物,所以有几个点直接影响到Docker镜像的构建方式:

  • 默认认证插件是caching_sha2_password,如果你有老应用用mysql_native_password连库,镜像里就需要显式兼容配置,或者应用端升级驱动。
  • 部分旧参数被移除或改名,比如innodb_file_format这类早期参数在8.4里已经没了。Dockerfile里如果残留了这些参数,mysqld会直接启动失败。
  • 官方包仓库的命名从mysql80-community变成了mysql84-community,安装时注意仓库名别搞混,yum repolist看到的名字就是mysql84-community。
  • 8.4版本对系统表空间、redo log容量等默认值做了调整,你自定义my.cnf时最好基于8.4的默认值去微调,别拿8.0的模板硬套。

2. Dockerfile设计:构建“能跑”的MySQL镜像要把握的关键点

2.1 基础层:环境变量、系统依赖和目录规划

写Dockerfile跟写shell脚本很像,先把环境捋顺了再谈安装。

第一步是明确基础镜像的tag。Ubuntu路线我推荐ubuntu:22.04,原因很朴素:22.04的glibc版本、openssl版本和MySQL 8.4官方bin的兼容性已经经过大量验证,24.04虽更新但不少依赖包在24.04下需要额外处理。CentOS路线就直接用centos:7,官方bin就是对着它编的,用别的版本反而别扭。

第二步是环境变量。这些变量会在构建时被RUN指令引用,也会在运行时被入口脚本读取。我习惯把MYSQL_ROOT_HOST、MYSQL_DATABASE、MYSQL_USER这些变量统一放在ENV段里,给后续初始化脚本用。同时把PATH里加上MySQL的bin目录,省得后面一堆绝对路径看着心烦。

第三步是创建目录并规划权限。容器里跑MySQL,目录结构要和官方一致,省得自己在配置里猜来猜去:

ENV MYSQL_DATA_DIR=/var/lib/mysql \ MYSQL_RUN_DIR=/var/run/mysqld \ MYSQL_LOG_DIR=/var/log/mysql RUN mkdir -p ${MYSQL_DATA_DIR} ${MYSQL_RUN_DIR} ${MYSQL_LOG_DIR} \ && chown -R mysql:mysql ${MYSQL_DATA_DIR} ${MYSQL_RUN_DIR} ${MYSQL_LOG_DIR}

别小看这一步。MySQL的mysqld进程在启动时要对datadir、socket目录、日志目录有写权限,而这些目录的属主必须是执行mysqld的用户。如果你用mysql用户启动,结果目录却是root的,启动会报“Permission denied”。这个属于可预见的坑,写进Dockerfile比事后排查省事多了。

注意:MySQL官方rpm/deb包装完以后会生成mysql系统用户,所以基础层不需要自己useradd,除非你的基础镜像特别精简。

2.2 安装层:MySQL官方仓库的配置技巧

安装MySQL 8.4,核心是要先把官方仓库装进去。Ubuntu和CentOS的仓库配置差异很明显,分开看。

Ubuntu 22.04安装官方仓库:

RUN apt-get update && apt-get install -y --no-install-recommends \ wget ca-certificates lsb-release gnupg \ && wget https://dev.mysql.com/get/mysql-apt-config_0.8.33-1_all.deb \ && echo "mysql-apt-config mysql-apt-config/select-server select mysql-8.4-lts" | debconf-set-selections \ && dpkg -i mysql-apt-config_0.8.33-1_all.deb \ && apt-get update \ && DEBIAN_FRONTEND=noninteractive apt-get install -y mysql-server \ && rm -rf /var/lib/apt/lists/*

这里有两个细节很容易翻车。第一,debconf-set-selections这一步是必须的,否则安装过程中会弹出交互式界面让你选MySQL版本,容器构建遇到交互就是死等。第二,如果基础镜像里没装lsb-release,mysql-apt-config脚本可能找不到系统版本信息而报错,所以基础包要装齐。

CentOS 7安装官方仓库:

RUN yum install -y https://dev.mysql.com/get/mysql84-community-release-el7-1.noarch.rpm \ && yum install -y mysql-community-server \ && yum clean all

这条命令看起来简单,但现实中会遇到GPG key验证失败,这是因为官方仓库的GPG密钥在2023年换过。解决办法是导入新密钥,或者临时加--nogpgcheck。但我不建议裸奔--nogpgcheck,正确做法是:

RUN rpm --import https://repo.mysql.com/RPM-GPG-KEY-mysql-2023

然后yum install时如果还报key不匹配,就把密钥文件和仓库配置里的GPG路径对齐一下。

安装完成后,还要顺手清理。Ubuntu的apt-get clean,CentOS的yum clean all,都是为了砍掉镜像里的缓存文件。别小看这几MB,积少成多,最后镜像体积能差出几百MB。MySQL这种重组件,构建完的镜像体积分分钟上1GB,容量控制要从小处抠。

2.3 初始化层:入口脚本设计的核心思路

官方镜像之所以好用,很大程度是因为它的入口脚本帮你做了初始化:首次启动时检测datadir是否为空,为空就执行mysqld --initialize-insecure,然后启动服务,再根据环境变量创建用户和数据库。我们自定义Dockerfile也要复刻这套逻辑,否则镜像只能算“装了MySQL的容器”,不能算“能开箱即用的MySQL服务”。

入口脚本我习惯命名为docker-entrypoint.sh,功能拆成四步:

#!/bin/bash set -euo pipefail # 1. 配置文件生成:根据环境变量动态写入 /etc/my.cnf if [ -n "${MYSQL_PASSWORD+x}" ]; then sed -i "s/^#max_connections=.*/max_connections=${MYSQL_MAX_CONNECTIONS:-200}/" /etc/my.cnf fi # 2. 数据目录初始化:判断是否首次启动 if [ ! -d "${MYSQL_DATA_DIR}/mysql" ]; then echo "Initializing MySQL data directory..." mysqld --initialize-insecure --user=mysql --datadir=${MYSQL_DATA_DIR} fi # 3. 服务启动:用 mysqld 直接前台运行 exec mysqld --user=mysql --datadir=${MYSQL_DATA_DIR}

这段脚本有一个很关键的判断——[ ! -d "${MYSQL_DATA_DIR}/mysql" ]。MySQL初始化成功后,datadir下会出现名为mysql的系统库目录,所以这个判断能准确区分“空数据目录”和“已初始化数据目录”。容器重启时,如果datadir已经存在,就跳过初始化直接启动,这个机制保证了数据不会在每次重启时被清掉。

初始化用的--initialize-insecure表示生成一个空的root密码,然后在启动前通过SQL去设置用户。也可以直接用--initialize生成随机密码再日志里找,但自动化的场景下,随机密码反而增加复杂度,我倾向用insecure模式然后自己在脚本里改密码。

注意:set -euo pipefail是必须的。-e保证任何一个命令失败都会让容器启动失败退出,避免MySQL没起来容器却“活”着的假象;-u防止变量未定义时静默通过;pipefail让管道命令的真实错误能暴露出来。少了这个,排查问题时你看到的现象会非常诡异。

2.4 数据持久化:VOLUME和目录权限的平衡

MySQL天生是有状态服务,镜像里无论如何都要把数据目录做成持久化。Dockerfile里直接声明VOLUME:

VOLUME ["/var/lib/mysql"]

声明VOLUME后,docker run时不加-v,数据也会落在容器可写层但无法被外部直接管理。真正要持久化,还得在docker run时挂载:

docker run -d --name mysql84 \ -v /data/mysql:/var/lib/mysql \ -p 3306:3306 \ my-mysql:8.4

这里有个权限细节:宿主机上的/data/mysql目录属主默认是root,而容器里的mysqld要用mysql用户写,于是启动时报“can't create/write to file”。解决方法是把宿主机目录的属主改成和容器内MySQL的UID一致。MySQL官方用户的UID通常是999(CentOS系)或999(Ubuntu系),但不同发行版也可能不同。保险做法是宿主机上执行:

mkdir -p /data/mysql chown 999:999 /data/mysql

这样容器内用户和宿主机目录权限就对齐了。如果基础镜像不同导致UID不一致,在Dockerfile里显式useradd -u 1001并让数据目录属主为1001,然后宿主机上chown 1001:1001 -R /data/mysql,道理一样。

2.5 健康检查:HEALTHCHECK的实用写法

镜像不能用起来就完事,还要告诉编排系统“什么时候算健康”。Dockerfile里的HEALTHCHECK指令可以定时探测:

HEALTHCHECK --interval=30s --timeout=5s --start-period=60s --retries=3 \ CMD mysqladmin ping -h 127.0.0.1 -u"$$MYSQL_USER" -p"$$MYSQL_PASSWORD" || exit 1

这里有个小坑——$$是对环境变量的转义,$MYSQL_USER会留到运行时才被替换,而不是在构建期就被展开。如果你写成单$,构建阶段就会被当成空变量替换掉,镜像里的健康检查命令就变成mysqladmin ping -h ... -u -p,语法直接报错。

--start-period=60s很重要。MySQL首次初始化+启动,在性能一般的机器上可能要三十秒到一分钟,如果没给足启动宽限期,健康检查会一直失败然后容器被判定unhealthy。你可以在运行时用docker inspect观察状态,如果反复出现unhealthy,第一件事就是看启动耗时是不是超过了start-period。

3. 两个可以抄作业的Dockerfile

3.1 Ubuntu 22.04路线完整实现

下面这个Dockerfile我实测过,构建产物可以直接拉到生产跑,字符集、时区、密码策略都做了合理的默认设置。

FROM ubuntu:22.04 MAINTAINER devops@example.com ENV DEBIAN_FRONTEND=noninteractive \ TZ=Asia/Shanghai \ MYSQL_DATA_DIR=/var/lib/mysql \ MYSQL_RUN_DIR=/var/run/mysqld \ MYSQL_LOG_DIR=/var/log/mysql \ MYSQL_ROOT_PASSWORD=root123 \ MYSQL_DATABASE=appdb \ MYSQL_USER=appuser \ MYSQL_PASSWORD=app123 RUN sed -i "s@http://.*archive.ubuntu.com@http://mirrors.aliyun.com@g; s@http://.*security.ubuntu.com@http://mirrors.aliyun.com@g" /etc/apt/sources.list \ && apt-get update \ && apt-get install -y --no-install-recommends wget ca-certificates gnupg lsb-release vim net-tools \ && wget https://dev.mysql.com/get/mysql-apt-config_0.8.33-1_all.deb \ && echo "mysql-apt-config mysql-apt-config/select-server select mysql-8.4-lts" | debconf-set-selections \ && dpkg -i mysql-apt-config_0.8.33-1_all.deb \ && apt-get update \ && DEBIAN_FRONTEND=noninteractive apt-get install -y mysql-server \ && rm -rf /var/lib/apt/lists/* \ && rm -f mysql-apt-config_0.8.33-1_all.deb # 自定义入口脚本 COPY docker-entrypoint.sh /usr/local/bin/ RUN chmod +x /usr/local/bin/docker-entrypoint.sh # 自定义配置 COPY my.cnf /etc/mysql/conf.d/custom.cnf EXPOSE 3306 VOLUME ["/var/lib/mysql"] ENTRYPOINT ["docker-entrypoint.sh"]

my.cnf内容按需给,我在这份里开了utf8mb4和合理的连接数:

[mysqld] character-set-server = utf8mb4 collation-server = utf8mb4_0900_ai_ci max_connections = 200 default-time-zone = '+08:00' innodb_buffer_pool_size = 512M innodb_flush_log_at_trx_commit = 1 skip-name-resolve

其中skip-name-resolve值得说两句:它让MySQL不再对客户端IP做反向DNS解析,能显著降低连接耗时,代价是用户授权表里的Host字段不能用域名只能写IP。容器环境里客户端IP是固定的容器网段,用IP授权就够了,所以我建议默认开。

3.2 CentOS 7路线完整实现

CentOS 7路线整体逻辑一样,但包管理和目录结构略有不同。下面是可复现的版本:

FROM centos:7 ENV TZ=Asia/Shanghai \ MYSQL_DATA_DIR=/var/lib/mysql \ MYSQL_RUN_DIR=/var/run/mysqld \ MYSQL_LOG_DIR=/var/log/mysql \ MYSQL_ROOT_PASSWORD=root123 \ MYSQL_DATABASE=appdb \ MYSQL_USER=appuser \ MYSQL_PASSWORD=app123 RUN yum install -y https://dev.mysql.com/get/mysql84-community-release-el7-1.noarch.rpm \ && rpm --import https://repo.mysql.com/RPM-GPG-KEY-mysql-2023 \ && yum install -y mysql-community-server \ && yum clean all \ && mkdir -p /var/run/mysqld \ && chown mysql:mysql /var/run/mysqld COPY docker-entrypoint.sh /usr/local/bin/ RUN chmod +x /usr/local/bin/docker-entrypoint.sh COPY my.cnf /etc/my.cnf EXPOSE 3306 VOLUME ["/var/lib/mysql"] ENTRYPOINT ["docker-entrypoint.sh"]

CentOS重点注意两点:第一,yum install时的“Failed to connect to repo.mysql.com”问题,多半是公网访问受限,替换成内部镜像源的仓库包即可,但记得GPG key要同步导入;第二,CentOS 7自带的numactl-libs可能版本不够,MySQL启动时报“libnuma.so.1: cannot open shared object file”,这时候手动yum install -y numactl-libs libaio就能解决。

my.cnf在CentOS路线的路径和Ubuntu不同,Ubuntu是/etc/mysql/conf.d/,CentOS则是直接覆盖/etc/my.cnf,这点容易搞混。

3.3 构建、启动和镜像体积实测

构建命令很直接:

docker build -t my-mysql:8.4-ubuntu .

构建过程第一次比较慢,因为要拉基础镜像、安装一堆依赖,大概两三分钟。成功后在当前目录执行:

docker run -d --name mysql84 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD=root123 \ -e MYSQL_DATABASE=appdb \ -e MYSQL_USER=appuser \ -e MYSQL_PASSWORD=app123 \ -v /data/mysql:/var/lib/mysql \ my-mysql:8.4-ubuntu

等十来秒,用docker ps看状态。如果STATUS是Up且(healthy),说明一切正常。然后进容器验证:

docker exec -it mysql84 mysql -uroot -proot123 -e "SELECT VERSION();"

我实测Ubuntu路线的镜像体积在1.2GB左右,CentOS路线约1.1GB,比官方镜像(约600MB)大了将近一倍。原因很实在:基础镜像本身大,再加上源码包安装时残留的doc、man等文件去不净。要瘦身,可以后续用多阶段构建做一层清理,删掉/usr/share/mysql下的测试文件和/var/cache。但生产环境里,稳定优先于体积,多几百MB的镜像并没有那么要命,反而你该关注的是容器启动速度和数据挂载安全性。

3.4 用环境变量控制配置:一个启动逻辑的示范

前面入口脚本里提到根据环境变量生成配置,这里演示一个可以复用的写法。我们在入口脚本里加入以下逻辑:

if [ ! -z "$MYSQL_DATABASE" ]; then # 等待 mysqld 真正就绪,注意此时尚未 exec mysqld,需要先手工拉起临时实例 mysqld --user=mysql --datadir=${MYSQL_DATA_DIR} --socket=${MYSQL_RUN_DIR}/mysqld.sock --skip-networking & for i in $(seq 1 30); do if mysqladmin ping --socket=${MYSQL_RUN_DIR}/mysqld.sock -uroot >/dev/null 2>&1; then break fi sleep 1 done mysql --socket=${MYSQL_RUN_DIR}/mysqld.sock -uroot <<EOF ALTER USER 'root'@'localhost' IDENTIFIED BY '${MYSQL_ROOT_PASSWORD}'; CREATE DATABASE IF NOT EXISTS \`${MYSQL_DATABASE}\`; CREATE USER IF NOT EXISTS '${MYSQL_USER}'@'%' IDENTIFIED BY '${MYSQL_PASSWORD}'; GRANT ALL PRIVILEGES ON \`${MYSQL_DATABASE}\`.* TO '${MYSQL_USER}'@'%'; FLUSH PRIVILEGES; EOF mysqladmin shutdown --socket=${MYSQL_RUN_DIR}/mysqld.sock -uroot -p${MYSQL_ROOT_PASSWORD} fi

这个写法的意义在于:把“初始化数据库、创建业务库、创建业务账号”全部交给环境变量驱动,同一个镜像给不同团队使用时,只需改docker run的环境变量,拿到手就是各自需要的数据库环境。这里的核心逻辑是在无网络监听状态下临时启动一个mysqld实例,用socket完成建库建用户,然后干净地关掉,最后再正式启动对外提供服务。

有一点要小心:mysqladmin shutdown之后,临时实例完全退出,日志里可能出现“Shutdown complete”字样,这是正常的。但如果shutdown后进程没有完全退出,后续正式启动会被锁文件卡住。稳妥的方式是临时实例启动后记录PID,shutdown时用kill兜底,或者加个循环等待进程退出。我实际加过这个循环:

while kill -0 $TEMP_MYSQLD_PID 2>/dev/null; do sleep 1 done

这样能保证入口脚本不会带着僵尸体往下走。

4. 实战中绕不开的坑

4.1 容器启动后MySQL一直退出

这个现象最常见,表现是docker run -d后容器很快变成Exited,日志里能看到mysqld报错。我统计了一下,九成是三个原因:

数据目录权限。日志里出现[ERROR] Can't open the mysql.plugin table或[ERROR] /usr/sbin/mysqld: Can't create/write to file '/var/lib/mysql/ib_logfile0',这基本可以断定是/var/lib/mysql属主不是mysql。处理方式前面已经说过了,宿主机上改属主。

残留的配置参数不兼容。8.4对很多老参数已经不支持,如果你把8.0时代模板里的innodb_file_format=Barracuda照搬到8.4,mysqld会直接报“Unknown variable”退出。解决方式是启动前用mysqld --verbose --help | grep逐个验证,或者干脆用官方默认配置做基准再微调。

日志文件锁冲突。容器异常退出后再启动,残留的/var/run/mysqld/mysqld.pid或socket文件没清掉,新进程起不来。入口脚本里加一句清理:

rm -f /var/run/mysqld/mysqld.sock /var/run/mysqld/mysqld.pid

放在初始化和启动之间,能解决大量“二次启动失败”的问题。

4.2 中文乱码和字符集问题

MySQL 8.4默认字符集已经是utf8mb4,但如果你在Dockerfile里没有显式指定,某些情况下连接层仍是utf8mb4_0900_ai_ci也不一定和预期一致。最彻底的做法是在my.cnf里写死服务端、客户端和连接层的字符集:

[client] default-character-set = utf8mb4 [mysql] default-character-set = utf8mb4 [mysqld] character-set-server = utf8mb4 collation-server = utf8mb4_0900_ai_ci

构建完成后进容器查看:

SHOW VARIABLES LIKE 'character%'; SHOW VARIABLES LIKE 'collation%';

重点看character_set_server和collation_server是否都已经是utf8mb4。如果只有server变了,但character_set_client还是latin1,那连接时还是会有问题。skip-character-set-client-handshake参数可以强制忽略客户端指定的字符集,但副作用是所有客户端都按服务端字符集走,慎用。

还有一道隐藏坑:如果你在初始化脚本里已经执行了建库建表,而那时字符集还没被正确设置,库表和索引的字符集就固化下来了,后续改my.cnf也不影响已存在的表。所以自定义镜像的初始化顺序必须是“先配好字符集再建库建表”,这个顺序写进入口脚本第一段,别乱。

4.3 CentOS 7的glibc兼容和仓库依赖谜题

CentOS 7的用户装MySQL 8.4,最常见的报错是:

error: Failed dependencies: libnuma.so.1()(64bit) is needed by mysql-community-server-8.4.x libaio.so.1()(64bit) is needed by mysql-community-server-8.4.x

这两个依赖在CentOS 7最小安装里没有,而yum又不会自动补全。Dockerfile里在装mysql-community-server前先显式装依赖:

RUN yum install -y https://dev.mysql.com/get/mysql84-community-release-el7-1.noarch.rpm \ && yum install -y libaio numactl-libs \ && yum install -y mysql-community-server

顺序很重要:先把libaio和numactl-libs装上,再装MySQL,yum在解析依赖时就会直接用已安装的包,不会再报错。

另一个可能是glibc版本问题。MySQL 8.4官方EL7用的glibc还是2.17起步,正常情况下CentOS 7的glibc完全够用。但如果你在基础镜像里动了glibc,比如用了某些第三方源升级过,就可能出现“version `GLIBC_2.28' not found”的诡异报错,这时候回退官方CentOS基础镜像、不要动glibc,是最省事的解法。

4.4 时区问题:容器和宿主机时间对不上

容器默认时区是UTC,MySQL的SYSTEM时区跟着容器走,于是NOW()会比北京时间晚8小时。日志和业务数据的时间对不上,排查问题时会非常头痛。

Dockerfile里已经设置了ENV TZ=Asia/Shanghai,但要注意,光设环境变量不一定会自动生成/etc/localtime。在Ubuntu系下,还要加一步:

RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \ && echo "Asia/Shanghai" > /etc/timezone

CentOS同理:

RUN cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime

然后my.cnf里配上default-time-zone = '+08:00'。这样应用层和数据库层的时间就是一致的了。我自己有一次排查线上订单时间差了8小时,最后发现就是容器时区没配,从那以后每个MySQL镜像必配时区,一步都不省。

4.5 几个高频的排查命令

遇到问题别急着怀疑人生,先用这几条命令把现场固定下来:

# 看容器实时日志 docker logs -f mysql84 # 进容器里手动启动 mysqld 看前台报错 docker exec -it mysql84 bash mysqld --user=mysql --console # 检查关键目录权限 docker exec -it mysql84 ls -ld /var/lib/mysql /var/run/mysqld /var/log/mysql # 检查端口监听 docker exec -it mysql84 netstat -tlnp | grep 3306 # 检查进程和PID文件 docker exec -it mysql84 ps aux | grep mysqld docker exec -it mysql84 cat /var/run/mysqld/mysqld.pid

mysqld --user=mysql --console这一条特别好用,它会让mysqld在前台直接输出日志,很多被日志文件吞掉的早期错误线索会在终端里直接刷出来。比如常见的数据目录找不到表空间、配置文件解析失败,都能一眼看到。

写在最后的一个小技巧

如果你打算把这套镜像纳入正式运维体系,可以在构建命令里加一个版本标签,比如my-mysql:8.4-ubuntu-1.0,同时在Dockerfile里用ARG BUILD_VERSION声明版本号,用LABEL version=$BUILD_VERSION标记。这样镜像和代码一样有了版本,出了事故也方便回溯是哪个构建版本引入的问题。我自己吃过大亏,有一次改了my.cnf里的空闲超时参数,忘了打标签,生产环境机器全被回收连接,排查了好几天才定位到是哪一版镜像。加了版本标签后,这类问题基本不会再有了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询