news 2026/9/9 15:29:33

K8s离线部署MySQL 5.7:全量离线包制作与实战指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
K8s离线部署MySQL 5.7:全量离线包制作与实战指南

简介:针对 Kubernetes 集群内网或离线环境中部署 MySQL 5.7 的常见问题,这份资源把镜像包与部署清单打包成套,适合没有外网拉取条件的运维、开发或学习人员使用。压缩包包含 7 个文件,其中有 4 个可直接应用的 YAML 清单、2 个 MySQL 镜像 tar 包以及 1 份启动说明,整体体积约 280.98MB。

YAML 文件覆盖了 Deployment、持久化卷、PVC 与 Service 等关键对象,镜像包可通过 docker load 方式导入本地,再配合 kubectl apply 快速启动服务,省去手工编写资源清单和镜像搬运的重复操作。启动说明文档则为首次使用者提供了导入与部署的先后顺序,降低上手门槛。

目前已有 1095 人学习/下载。除 MySQL 5.7 主镜像外,还附带了另一版本镜像包,便于在离线环境下完成可持久化存储的 MySQL 部署,同时帮助理解 Kubernetes 上有状态应用对存储、网络与服务暴露的基本设计思路。

1. 为什么内网交付最头疼的"全量"两个字

这两年做项目交付,凡是遇到 K8s 环境,十有七八是内网隔离的。客户机房不连外网,你在线 Docker Hub 拉镜像?不存在的。你想着用 pip/apt/yum 二进制的习惯在 K8s 场景里也这么干?同样行不通。Kubernetes 部署 MySQL 5.7 这个需求,单看思路并不复杂,真正让人崩溃的是"全量离线包"这五个字里藏着的依赖闭环——镜像、编排文件、初始化脚本、存储配置、访问入口,任何一环少带了,到了现场就是干瞪眼。

我最初接过一个典型的项目:现场是 K8s 1.20 集群,节点没有外网,要求在上面部署一套 MySQL 5.7.44,用来承接业务系统的订单库。我当时天真地以为,只要把 mysql:5.7 的镜像打成 tar 包带进去,再docker load一下,写个 Deployment 就完事了。结果到现场发现完全不是那么回事——没有镜像仓库,节点是 containerd,编排文件里没考虑存储类,密码和配置没有妥善管理,初始化脚本也没地方放。最后折腾了快一天,才把一套相对完整的全量离线包方案跑通。

这篇文章就是想把这份经验完整沉淀下来:你在内网 K8s 环境部署 MySQL 5.7,到底需要准备哪些东西、怎么制作离线包、到了现场怎么执行,以及最容易在哪里翻车。不管你是要部署一整套集群,还是单节点轻量跑业务,按这个思路做都能少走很多弯路。

先给结论:一套可复现的全量离线包 = MySQL 5.7 镜像 + 配置模板(my.cnf)+ 初始化 SQL + K8s 编排文件 + 辅助脚本。缺少其中任何一个,到了离线环境都会出现"看着包挺全,实际用不了"的尴尬。

2. 离线包到底该装哪些东西:MySQL 5.7 的资源清单

2.1 镜像选择:不要只盯着版本号

MySQL 5.7 官方镜像在 Docker Hub 上最后发布的是 5.7.44。社区里不少镜像还停留在 5.7.36、5.7.40 左右,我能给你的建议是:选择 5.7.44 或者你能确认的、在目标环境验证过的版本,并把它精确到 tag 写死。很多人在线部署习惯了latest,到离线环境就翻车——因为你导出时是 latest,现场加载后还是 latest,但镜像内容已经被后续更新污染过,无法保证一致性。

还要注意 CPU 架构。如果你的部署机器是 x86_64,那就导出 amd64 的镜像;如果是国产化 arm64 环境,就必须导出 arm64 版本。最稳妥的做法是利用docker --platform参数主动拉取对应架构的镜像,而不是靠 Docker Hub 自动选择,避免你在 x86 上拉了个 amd64,结果现场是 arm 节点,直接启动失败。

2.2 编排层资源:StatefulSet 是关键

我们知道 MySQL 属于有状态服务,K8s 里的 Deployment 并不适合直接承载数据库。规划的编排文件至少包括:

  • StatefulSet:提供稳定的网络标识(如 mysql-0.mysql-hs.namespace.svc),配合 PersistentVolumeClaim 模板实现一实例一存储。
  • Headless Service:为 StatefulSet 提供稳定的 DNS 解析地址。
  • ConfigMap:存放 my.cnf 配置片段。
  • Secret:存放 MySQL root 密码(也可以加业务账号密码)。
  • PVC:数据卷声明,绑定到实际的 PV。
  • 普通 Service:用于外部或同集群业务访问。

这几个文件的 apiVersion 在不同 K8s 版本里有差异。以 1.20 为例,StatefulSet 使用的 apiVersion 是apps/v1,Secret/ConfigMap 是v1,Service 是v1。如果你现场环境是更老的 1.16 以下,还可能出现extensions/v1beta1的写法,但现实项目中这类老集群已经很少见,新增部署优先按apps/v1处理即可。

2.3 数据层:初始化脚本和存储准备

MySQL 官方镜像支持把.sql.sh脚本放到/docker-entrypoint-initdb.d/目录,容器首次初始化空数据目录时会自动执行。这个机制是离线包里非常关键的组成部分,你可以借此完成建库、建用户、设置时区、导入基础数据等动作。

存储准备方面,正常集群应该有一个默认 StorageClass,你可以直接用动态供给创建 PV;如果是裸机环境没有 StorageClass,就得提前创建 PV 并指定nodeAffinity绑定到固定节点,或者使用 local 持久化卷。这块我在第 5 部分单独讲,因为它是排障重灾区。

3. 制作离线包的实操流程:从镜像导出到编排文件定型

3.1 镜像导出与压缩

在有外网的机器上,拉取镜像后导出为 tar 包。实操我一般用如下命令:

docker pull mysql:5.7.44 mkdir -p mysql5.7-offline/images docker save mysql:5.7.44 | gzip > mysql5.7-offline/images/mysql-5.7.44.tar.gz

这样打出来的包体积大约在 500MB 左右(MySQL 5.7 镜像本身 400 多MB),要注意现场传输效率和 U 盘文件系统格式。另外有人喜欢直接docker save -o mysql.tar mysql:5.7.44,不压缩也行,但现场拷盘时 tar.gz 明显更友好。

如果你现场不是 Docker,而是 containerd(现在 K8s 1.24+ 默认运行时已经是 containerd),那你在制作端也别只想着 docker 体系。建议在离线包目录里同时放一份原始 tar.gz,因为ctr images import是可以直接识别 docker save 格式的。换句话说,docker save 导出的镜像是通用格式,现场既能 docker load,也能 ctr images import

3.2 编排文件模板

我习惯把编排文件放在manifests/目录下,与 images 平级。以下是一份经过现场验证的 StatefulSet 示例(节选,完整结构你可以在自己的模板里保持同样的骨架):

apiVersion: v1 kind: Secret metadata: name: mysql-secret namespace: database type: Opaque stringData: root-password: "YourStrongPassw0rd" mysql-user: "appuser" mysql-password: "AppUserPassw0rd"
apiVersion: v1 kind: ConfigMap metadata: name: mysql-config namespace: database data: my.cnf: | [mysqld] character-set-server=utf8mb4 collation-server=utf8mb4_general_ci lower_case_table_names=1 max_connections=500 innodb_buffer_pool_size=1G sql_mode=STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION default-time-zone='+08:00'
apiVersion: apps/v1 kind: StatefulSet metadata: name: mysql namespace: database spec: serviceName: mysql-hs replicas: 1 selector: matchLabels: app: mysql template: metadata: labels: app: mysql spec: containers: - name: mysql image: mysql:5.7.44 ports: - containerPort: 3306 env: - name: MYSQL_ROOT_PASSWORD valueFrom: secretKeyRef: name: mysql-secret key: root-password volumeMounts: - name: data mountPath: /var/lib/mysql - name: mysql-config mountPath: /etc/mysql/conf.d/my.cnf subPath: my.cnf volumes: - name: mysql-config configMap: name: mysql-config volumeClaimTemplates: - metadata: name: data spec: accessModes: [ "ReadWriteOnce" ] resources: requests: storage: 20Gi

3.3 初始化 SQL 的归置

初始化脚本里我一般放建库建表、业务账号授权、时区函数等。示例:

CREATE DATABASE IF NOT EXISTS business DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; CREATE USER IF NOT EXISTS 'appuser'@'%' IDENTIFIED BY 'AppUserPassw0rd'; GRANT ALL PRIVILEGES ON business.* TO 'appuser'@'%'; FLUSH PRIVILEGES;

把这个脚本放进离线包的init-sql/目录。使用的方案是把整个 SQL 目录通过 ConfigMap 挂载到容器,或者用 initContainer 拷贝进去。这里有个细节:直接挂载单个文件到/docker-entrypoint-initdb.d/是可以的,但如果你有多个 SQL 文件且有先后顺序,建议挂载整个目录,因为 MySQL 官方镜像会按文件名顺序执行,而不是按挂载顺序。

3.4 生成校验文件

离线包传到现场后,很难保证没有损坏。制作完成后我会生成一个 sha256 校验信息:

cd mysql5.7-offline find . -type f -exec sha256sum {} \; > SHA256SUMS

到了现场先跑一下sha256sum -c,确认镜像和编排文件没有在拷贝过程中出现损坏。虽然是老生常谈,但我见过真的有人现场镜像加载失败,最后发现是 U 盘格式化问题导致文件截断。

4. 集群里的部署执行手册:离线环境照抄版

4.1 镜像导入

现场若是 containerd 运行时,登录到每一个需要运行 MySQL pod 的节点,执行:

# 选择对应节点,一般用 kubectl get nodes 查看 ctr -n k8s.io images import mysql5.7-offline/images/mysql-5.7.44.tar.gz

注意命名空间参数-n k8s.io不能漏。containerd 在不同发行版里的k8s.ionamespace 是 K8s 镜像的默认位置,如果你 import 到默认defaultnamespace,kubelet 在拉取镜像的时候根本找不到。

如果是 docker 运行时,则执行:

docker load -i mysql5.7-offline/images/mysql-5.7.44.tar.gz docker images | grep mysql

这一步做完,一定要在节点上确认镜像 tag 完整显示为mysql:5.7.44,而不是<none>:<none>。在现场遇到过镜像 tar 里没有保留 tag 的情况,加载进去一堆<none>,pod 照样拉不到。

4.2 加载编排文件

把离线包里的 manifests 传到能执行 kubectl 的机器上(通常是堡垒机或 master 节点),按顺序执行:

kubectl create namespace database kubectl apply -f manifests/secret.yaml kubectl apply -f manifests/configmap.yaml kubectl apply -f manifests/service.yaml kubectl apply -f manifests/statefulset.yaml

注意命名空间。如果你的编排文件里没有写死 namespace,那apply之前要确认当前上下文 namespace 是否正确,或者用-n database参数显式指定,避免 Yaml 应用到默认命名空间里去,后面业务方访问路径对不上。

4.3 等待 Pod 就绪与验证

执行之后观察:

kubectl -n database get pod -o wide kubectl -n database logs -f mysql-0 --tail=100

第一次启动时,MySQL 官方镜像会比较慢,因为它要初始化数据目录、执行 initdb 目录里的脚本。如果你看到日志停留在ready for connections出现,基本就成功了一半。此时可以进入 pod 做一次连接测试:

kubectl -n database exec -it mysql-0 -- mysql -uroot -p'YourStrongPassw0rd' -e "SELECT VERSION();"

如果输出5.7.44,部署算真正跑通了。接下来再验证从集群内部另一个 pod 是否能通过 Service 访问数据库——这才是业务方真正关心的连通性。

4.4 暴露访问入口

如果业务方需要从集群外直连,你还需要一个 NodePort 类型的 Service 或者 Ingress,而不是直接在 StatefulSet 里改 hostPort(那会限制 pod 调度位置)。示例 Service:

apiVersion: v1 kind: Service metadata: name: mysql-nodeport namespace: database spec: type: NodePort selector: app: mysql ports: - name: mysql port: 3306 targetPort: 3306 nodePort: 30306

5. 持久化、权限和初始化脚本:最容易踩的三个坑

5.1 存储类不存在时怎么处理

很多离线集群并没有配置 StorageClass,或者 StorageClass 的 provisioner 依赖云厂商组件,但内网环境根本没部署。这时候你用volumeClaimTemplates动态创建 PVC 大概率会卡在 Pending 状态。排查方法就是kubectl describe pvc,看到waiting for a volume to be created, either by external provisioner这类报错,基本确定是 StorageClass 问题。

比较常见的离线方案是手工创建 PV,再利用volumeClaimTemplates自动匹配。为 MySQL 创建一块 20Gi 的 local PV 示例:

apiVersion: v1 kind: PersistentVolume metadata: name: mysql-pv-1 spec: capacity: storage: 20Gi volumeMode: Filesystem accessModes: - ReadWriteOnce persistentVolumeReclaimPolicy: Retain storageClassName: local-storage local: path: /data/mysql-1 nodeAffinity: required: nodeSelectorTerms: - matchExpressions: - key: kubernetes.io/hostname operator: In values: - node02

注意persistentVolumeReclaimPolicy我建议设成Retain,别用Delete。数据库数据卷如果因为误操作被删除 PV,那数据就彻底没了,这不是开玩笑的。

5.2 数据目录权限和挂载问题

MySQL 官方镜像里的 mysqld 进程是以mysql用户运行的。容器第一次启动要往/var/lib/mysql写文件,如果挂载的 PV 目录权限不对,容器会直接报mysqld: Can't create/write to file '/var/lib/mysql/ibtmp1'然后退出。

以前我在一个 local PV 场景下就踩过这坑:宿主机/data/mysql-1的所有者是 root,权限 755,MySQL 容器根本写不进去。解决办法有两类:

  • 宿主机上先chown -R 999:999 /data/mysql-1(镜像内置 mysql 用户 UID 是 999)。
  • 或者给容器加securityContext
securityContext: fsGroup: 999 runAsUser: 999

我建议两种都配上。光靠 fsGroup 有时候对 local PV 也不一定生效,因为宿主机目录本身权限不对,fsGroup 不会自动执行 chown。正确姿势是:要么在宿主机把目录所有权改成 999,要么在 PV 上做初始化时用 initContainer 执行 chown:

initContainers: - name: volume-permission image: busybox:1.36 command: ["sh", "-c", "chown -R 999:999 /var/lib/mysql"] volumeMounts: - name: data mountPath: /var/lib/mysql

这个 initContainer 需要在你的离线包里也带上 busybox 镜像,否则现场没镜像照样起不来。很多人的离线包之所以"看着全,实际缺",往往就是少了这种辅助镜像。

5.3 初始化脚本执行时机

官方镜像的机制是:只在数据目录为空时执行/docker-entrypoint-initdb.d/下的脚本。如果数据目录里已经有内容(比如之前初始化失败但目录已创建了文件),脚本就不会再执行。这是个好机制,但也坑了不少人:第一次启动时没把 SQL 脚本挂进去,后来补挂了脚本发现不生效;或者数据目录被初始化了一半,脚本执行失败,修复后再重启,脚本不跑了,导致库和账号都不完整。

给你一个可复现的检查思路:启动时观察日志中是否有Importing SQL ...之类的关键字。如果没有,说明容器认为数据目录非空,跳过了初始化。此时你不该反复删 PVC(除非是测试环境),而应该手动进入容器执行 SQL:

kubectl -n database exec -it mysql-0 -- mysql -uroot -p'YourStrongPassw0rd' < init.sql

或者把脚本临时放到容器里再 source。对于正式环境,我更推荐把基线 SQL 的管理从前置初始化移到独立 job 或配置管理流程里,避免太依赖官方镜像初始化机制。

6. 排错链:从镜像导入到 pod Running 的完整排查路径

6.1 镜像导入后 Pod 还是 ImagePullBackOff

这个场景我在多个集群中遇到过。要么镜像根本没导入到目标节点,要么 tag 不对,要么没有私有仓库导致 K8s 默认要去 Docker Hub 拉取。排查路径是:

# 登录对应节点 crictl images | grep mysql # 或者 docker images | grep mysql

确认镜像确实在节点上。如果镜像在,但状态还是 ImagePullBackOff,检查 pod 的 image 字段有没有写错版本号,比如 YAML 里写 mysql:5.7 但本地只有 mysql:5.7.44。我的建议是无论什么环境,编排文件里都写完整的 tag,别用简写。

如果你现场搭了私有镜像仓库(例如 registry:2 离线部署),还要注意 containerd/docker 的insecure-registries配置。默认 HTTPS 仓库没有证书会直接拒绝 pull,很多人第一次搭离线仓库就是栽在只有 HTTP 没有配置信任上。

6.2 CrashLoopBackOff 与权限、配置、内存

pod 一直重启,第一件事永远先看日志:

kubectl -n database logs mysql-0 --tail=50

常见几类:

  • 权限错误:日志里带Permission denied,去查数据目录所有者和 fsGroup。
  • 配置文件错误:比如 sql_mode 写错,mysqld 起不来。这种最好在制作离线包时,先本地用mysqld --verbose --help验证一遍参数,再到现场调。
  • OOMKilled:pod 状态是OOMKilled,说明内存 limit 设置太小。MySQL 5.7 的 innodb_buffer_pool_size 如果给了 1G,而容器 limit 只有 512Mi,几乎必挂。建议给 MySQL pod 分配至少 2Gi 内存,缓冲池控制在容器内存的 50%-60% 左右。

6.3 连接报 1045 和字符集问题

部署成功后,业务方连接时报Access denied或者中文乱码,别急着改配置文件。先检查 root 密码 Secret 有没有被正确注入,再检查 my.cnf 里的character-set-server是否真的生效:

SHOW VARIABLES LIKE 'character_set_server'; SHOW VARIABLES LIKE 'lower_case_table_names';

这里有个需要特别提醒的:lower_case_table_names参数在 MySQL 5.7 初始化之后修改可能不生效,或者在部分场景下导致表名大小写行为不一致。必须在初始化之前就确定好这个参数,写进 ConfigMap。尤其业务方如果从 Windows 开发环境搬过来,经常要求设成 1(不区分大小写),但如果初始化时是默认值 0,后面再去改,很容易出现表找不到的错误。

6.4 连接地址和 Service 问题

同集群内访问 MySQL,推荐用mysql-0.mysql-hs.database.svc.cluster.local:3306这样的 DNS 地址。如果业务 pod 报找不到主机名,先确认 Headless Service 已经存在:

kubectl -n database get svc kubectl -n database get endpoints mysql-hs

如果 endpoints 里没有 IP,说明 selector 没匹配到 pod 标签。这种排查往往花的时间比部署本身还长,但套路是固定的:先排 DNS,再排 endpoints,再排网络策略。

7. 一点运维经验收尾

这套全量离线包方案后续还可以继续扩展,比如加一份mysqld_exporter镜像和 ServiceMonitor,把监控也纳入离线体系;或者把 MySQL 部署逻辑打包成 Helm chart,做版本化交付。不过不管怎么扩展,核心原则都是一样的:离线包的每一份资源都必须经过目标环境验证,不要想当然认为"在线能跑离线就能跑"

我个人在多次交付后的感觉是,把时间花在前期资源梳理和文件校验上,比到现场临时 debug 要节省得多。离线部署最耗时的从来不是 K8s 本身,而是"少带了一个辅助镜像""存储类没确认""初始化脚本时机不对"这些看似不起眼的细节。你只要提前把这几点理清楚,MySQL 5.7 在 K8s 离线环境的部署其实就是机械执行而已。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/9 15:29:28

富士通fi6130扫描仪驱动下载安装与排错实战指南

简介&#xff1a;富士通fi6130扫描驱动是面向fi-6130文档扫描仪用户的官方驱动资源&#xff0c;旨在解决设备与操作系统之间的通信兼容问题&#xff0c;提升扫描速度、图像质量与功能稳定性。压缩包共652个文件&#xff0c;体积约306.56MB&#xff0c;包含dll动态库、exe安装程…

作者头像 李华
网站建设 2026/9/9 15:26:46

STM32+ESP8266 TCP服务器实战:AT指令时序与状态机详解

简介&#xff1a;面向嵌入式初学者与物联网开发者&#xff0c;资源包基于意法半导体的STM32F103C8T6微控制器和ESP8266 WiFi模块&#xff0c;构成了一个以串口通讯控制无线模块的完整TCP服务器工程。它演示了如何通过AT指令集配置Wi-Fi模块、建立和关闭TCP连接&#xff0c;并配…

作者头像 李华
网站建设 2026/9/9 15:25:11

BOM部件替换实战:从参数框架到EDA工具链的完整方法论

1. 我为什么一直在折腾BOM部件替换这件事 干电子硬件这行的人&#xff0c;估计都经历过这种场景&#xff1a;样品阶段你画好一块板子&#xff0c;BOM表列得清清楚楚&#xff0c;结果一到试产&#xff0c;采购跑过来说某某料缺货、交期八周起、代理商那边价格翻了三倍。这个时候…

作者头像 李华
网站建设 2026/9/9 15:24:21

基于PyTorch的图像分类完整训练框架搭建实践

1. 框架整体设计与目录结构 先讲讲为什么我最终会沉淀出这么一套"基于 PyTorch 的图像分类完整训练框架"。早些年做图像分类项目&#xff0c;基本是今天写一个脚本训 ResNet&#xff0c;明天复制一个脚本调 DenseNet&#xff0c;后天又在另一个文件夹里堆一个 Effici…

作者头像 李华