简介本资源是一套面向Kubernetes初学者与运维工程师的MySQL 5.7全量离线部署方案专为无外网环境或需快速复现生产级数据库服务的场景设计解决K8s集群中MySQL稳定部署、数据持久化与启动流程标准化等核心问题。压缩包共7个文件含4个关键YAML涵盖Deployment、Service、PersistentVolume及PV Claim配置、2个MySQL 5.7离线镜像tar包已适配K8s容器运行时以及1份清晰的启动说明txt结构精简、开箱即用。资源大小为280.98MB所有YAML文件经实测验证无需修改即可直接kubectl apply部署配合load方式导入预置数据显著降低环境适配成本。目前已有1098人学习下载适合需要快速搭建高可用MySQL底座、理解Stateful应用在K8s中编排逻辑、掌握PV/PVC绑定与镜像离线加载实践的技术人员。1. Kubernetes 部署 MySQL 5.7 全量离线包别被“在线 Helm 一键部署”骗了内网环境里它连镜像都拉不下来你手头正要给某高校实验室的离线科研平台部署一套 MySQL 5.7 服务——网络策略严格节点完全断外网连docker pull都会卡在trying to pull repository十分钟不动。这时候有人甩给你一句“用 Helm 装 mysql三行命令搞定”你信了结果helm install bitnami/mysql直接报错Failed to fetch https://charts.bitnami.com/bitnami/index.yaml: Get https://...: dial tcp: lookup charts.bitnami.com: no such host。不是 Helm 不行是它根本没解决「镜像、Chart、初始化脚本、配置模板、依赖 CRD」这五样东西全得提前塞进内网这个核心问题。这套 Kubernetes 部署 MySQL 5.7 全量离线包就是专治这种场景它不依赖任何外部源所有组件含 MySQL 5.7 官方二进制、定制化 StatefulSet YAML、initContainer 初始化脚本、健康探针逻辑、RBAC 权限定义、ConfigMap 持久化配置全部打包为可验证、可审计、可复现的离线资源集合。适合正在搭建金融测试沙箱、军工仿真集群、教育私有云或医疗影像平台的运维和 DevOps 工程师——你不需要懂 Operator 原理但必须确保 MySQL 在 K8s 里启动就可用、重启不丢配置、扩容不崩主从。2. 离线包结构解析与环境兼容性确认看清这 7 个目录再决定要不要解压全量离线包不是 zip 一扔就完事。它本质是一套经过生产级验证的「Kubernetes 就绪型 MySQL 发行版」结构设计直指离线部署的三大死穴镜像不可达、配置不可控、状态不可溯。我一般拿到包第一件事不是tar -xzf而是tree -L 2扫一眼骨架确认关键路径是否存在、版本是否匹配当前集群能力。下面这张表是我过去三年在 12 个不同客户现场反复验证后提炼出的「必检项清单」每一条都对应一个曾导致部署中断的真实翻车点目录路径必须存在作用说明版本强关联项常见缺失后果./images/✅存放已导出的 MySQL 5.7 镜像 tar 包含mysql:5.7.42及其基础镜像debian:11-slimmysql:5.7.42非5.7latestkubectl apply后 Pod 卡在ImagePullBackOff日志显示Failed to pull image mysql:5.7./charts/✅精简版 Helm Chart无 externalDependenciesvalues.yaml 已预置离线参数Chart API v2apiVersion: v2helm template报错apiVersion v1 is not valid因旧版 Helm 3 不兼容 v1 Chart./manifests/✅纯 YAML 清单StatefulSet Service PVC ConfigMap Secret可绕过 Helm 直接kubectl applyapp.kubernetes.io/version: 5.7.42标签写死自动化脚本注入版本号失败导致监控系统识别为unknown./scripts/✅load-images.sh批量导入镜像、gen-secret.sh生成 base64 加密密码、validate-pvc.sh检查 StorageClass 是否就绪脚本中DOCKER_REGISTRYharbor.internal为占位符需手动替换镜像加载后 tag 错误如localhost:5000/mysql:5.7.42而非预期harbor.internal/mysql:5.7.42./configs/✅my.cnf完整模板含innodb_buffer_pool_size1G、max_connections500等生产调优项支持挂载覆盖skip-host-cache和skip-name-resolve已启用DNS 解析超时导致 MySQL 启动 hang 住Pod 一直CrashLoopBackOff./docs/⚠️建议COMPATIBILITY.md明确标注支持的 K8s 版本1.20–1.25、SECURITY.md密码加密方式、root 访问限制说明K8s 1.26 不支持extensions/v1beta1API在新集群上kubectl apply报错no matches for kind Deployment in version extensions/v1beta1./verify/✅sha256sum.txt所有文件校验和、verify-integrity.sh一键校验包完整性校验和基于sha256sum -b生成解压后文件损坏未被发现MySQL 启动时报mysqld: error while loading shared libraries: libaio.so.1提示./verify/verify-integrity.sh是我每次解压后的强制动作。它不只是比对 sha256还会检查./images/*.tar是否能被docker load正确识别通过docker load -i xxx.tar 21 | head -n 5截取输出判断。曾有个客户包里mysql-5.7.42-debian11.tar实际是mysql-8.0.33的镜像校验和对得上但docker inspect显示MajorVersion: 8—— 这种低级错误靠人工看文件名根本发现不了。2.1 确认你的 Kubernetes 集群满足最低门槛别急着跑脚本。先执行这三条命令把集群底细摸清。很多“部署失败”其实卡在 API 版本或插件缺失上而不是 MySQL 本身# 查看 K8s 主版本注意1.26 移除了大量 deprecated API kubectl version --short | grep Server # 示例输出Server Version: v1.24.15 → ✅ 兼容v1.26.0 → ❌ 需降级或打补丁 # 检查默认 StorageClass 是否 ReadyMySQL 必须用动态 PV kubectl get storageclass -o wide | awk $2 ~ /true/ {print $1, $3} # 输出应类似standard kubernetes.io/aws-ebs → ✅若为空或全是 false → ❌ 需先配置 # 验证 CoreDNS 是否正常影响 initContainer 中的域名解析 kubectl exec -it $(kubectl get pod -n kube-system -l k8s-appkube-dns -o jsonpath{.items[0].metadata.name}) -n kube-system -- nslookup kubernetes.default.svc.cluster.local # 返回 server cant find ...: NXDOMAIN 是严重问题initContainer 会卡死为什么强调 StorageClassMySQL 5.7 对磁盘 I/O 敏感离线包里的 StatefulSet 默认使用volumeClaimTemplates动态申请 PVC。如果集群没配 StorageClass或者配了但 provisioner 插件如rancher.io/local-path没装PVC 会长期处于Pending状态Pod 根本起不来。我见过最玄学的一次StorageClass 存在但allowVolumeExpansion: false后续想扩容量时kubectl patch pvc直接报错只能删库重来。2.2 离线镜像加载docker load不是终点ctr import才是内核真相离线包里的./images/目录下通常有 3~4 个.tar文件。你以为for i in ./images/*.tar; do docker load -i $i; done就完事了错了。Docker daemon 只是容器运行时之一Kubernetes 现在主流用 containerd。如果你的节点kubectl get node -o wide显示CONTAINER-RUNTIME是containerd://1.6.30那docker load加载的镜像 containerd 根本看不见。正确做法分两步# 第一步用 ctrcontainerd 客户端直接导入推荐绕过 Docker daemon sudo ctr -n k8s.io images import ./images/mysql-5.7.42-debian11.tar sudo ctr -n k8s.io images import ./images/debian-11-slim.tar # 第二步确认镜像已注册到 containerd 的命名空间 sudo ctr -n k8s.io images list | grep -E (mysql|debian) # 应输出类似 # docker.io/library/mysql:5.7.42 application/vnd.docker.distribution.manifest.v2json sha256:abc123... 1.2GiB io.cri-containerd.imagemanaged注意-n k8s.io是关键参数。containerd 为 Kubernetes 预设了k8s.io命名空间Kubelet 默认从此空间拉取镜像。如果漏掉-n k8s.io镜像会导入到默认default空间Kubelet 找不到照样ImagePullBackOff。这是血泪经验——某公司 200 节点集群因统一脚本忘了加-n导致 MySQL 全量滚动更新失败回滚耗时 47 分钟。2.3 配置文件预处理三个最容易写错的路径离线包的./configs/my.cnf是通用模板但有三处必须手工改否则 MySQL 启动即崩溃socket路径必须与 StatefulSet 中 volumeMount 一致./manifests/mysql-statefulset.yaml里定义了volumeMounts: - name: mysql-socket mountPath: /var/run/mysqld所以my.cnf里必须写[client] socket /var/run/mysqld/mysqld.sock # ❌ 错/tmp/mysqld.sockpid-file路径必须可写且与容器用户 UID 匹配MySQL 5.7 镜像默认以 UID 999 运行非 root而/var/run下目录常属 root。解决方案是在my.cnf中指定[mysqld] pid-file /var/lib/mysql/mysqld.pid # ✅ 此路径在 volumeMount 的 /var/lib/mysql 下UID 999 可写log-error日志路径必须指向空目录且不能是/dev/stderrKubernetes 日志采集依赖容器 stdout/stderr但 MySQL 5.7 的log-error /dev/stderr会导致日志乱码因 mysqld 写的是二进制格式。正确做法[mysqld] log-error /var/log/mysql/error.log并在 StatefulSet 中增加 volumeMount- name: mysql-log mountPath: /var/log/mysql这三个路径错一个kubectl logs -f mysql-0就只能看到CrashLoopBackOffkubectl exec -it mysql-0 -- cat /var/log/mysql/error.log才能看到真实错误Cant start server: Bind on unix socket: Permission denied或Could not open required defaults file: /etc/my.cnf。3. 部署全流程实操从kubectl apply到主从自动构建离线包的价值不在“能装”而在“装完就能用”。这一节带你走通从零到主从集群的完整链路每一步都附带验证命令和失败信号。别跳步有些坑就藏在第 3 步。3.1 创建专用命名空间与 RBAC 权限MySQL 是有状态服务必须隔离资源。离线包默认不创建 namespace这是故意为之——避免污染默认环境。# 创建 mysql-system 命名空间名称可自定义但需同步改 manifests 中所有 namespace 字段 kubectl create namespace mysql-system # 应用 RBAC离线包中 ./manifests/rbac.yaml 已预置最小权限 kubectl apply -f ./manifests/rbac.yaml -n mysql-systemrbac.yaml关键点ServiceAccount: mysql-sa绑定Role: mysql-role仅允许get/watch本 namespace 的 EndpointsClusterRoleBinding未创建杜绝跨 namespace 权限泄露Secret操作权限仅限create用于初始化 root 密码不开放list或delete验证kubectl auth can-i list secrets -n mysql-system --as system:serviceaccount:mysql-system:mysql-sa应返回no。如果返回yes说明 RBAC 过宽有安全风险。3.2 初始化 Secret密码不能硬编码但必须离线生成离线包提供./scripts/gen-secret.sh它不是简单echo pass | base64而是调用openssl rand -base64 12生成高强度随机串并写入./manifests/mysql-secret.yaml# 运行脚本会提示输入 root 密码留空则自动生成 ./scripts/gen-secret.sh # 查看生成的 Secret已 base64 编码但明文不落地 kubectl apply -f ./manifests/mysql-secret.yaml -n mysql-systemmysql-secret.yaml结构精简apiVersion: v1 kind: Secret metadata: name: mysql-root-pass namespace: mysql-system type: Opaque data: password: cGFzc3dvcmQxMjM # base64(password123)为什么不用 Helm--set因为helm install --set auth.rootPasswordxxx会把密码明文留在helm history和configmap中审计不通过。离线包坚持 Secret 独立管理符合等保 2.0 对口令存储的要求。3.3 部署 StatefulSet关注 PVC 绑定与 InitContainer 日志这是最关键的一步。离线包的./manifests/mysql-statefulset.yaml是为离线优化过的kubectl apply -f ./manifests/mysql-statefulset.yaml -n mysql-system必须盯住的三个现象kubectl get pvc -n mysql-systemmysql-data-mysql-0状态应为Bound非Pendingkubectl get pod -n mysql-systemmysql-0状态应从Init:0/1→Init:1/1→Runningkubectl logs -f mysql-0 -n mysql-system -c init-mysql看到Initializing MySQL database...和mysqld is aliveInitContainer 名叫init-mysql它干三件事检查/var/lib/mysql是否为空非空则跳过初始化执行mysqld --initialize-insecure因离线环境无法联网下载 SSL 证书创建mysql数据库并授权root%密码来自 Secret如果卡在Init:0/1大概率是 PVC 未就绪卡在Init:1/1kubectl logs mysql-0 -c init-mysql会显示Cant connect to local MySQL server through socket /var/run/mysqld/mysqld.sock—— 这说明my.cnf的 socket 路径错了见 2.3 节。3.4 主从自动构建用replicas: 3触发内置复制链离线包的 StatefulSet 默认replicas: 1但只需改一个值就能激活主从架构# 编辑 StatefulSet将 replicas 改为 3 kubectl edit statefulset mysql -n mysql-system # 修改 spec.replicas: 1 → 3保存退出 # 观察 Pod 启动顺序StatefulSet 保证有序 kubectl get pod -n mysql-system -w # 应看到mysql-0 → mysql-1 → mysql-2 依次 Running背后的自动化机制mysql-0启动时initContainer 检测到hostname -f为mysql-0.mysql-headless.mysql-system.svc.cluster.local判定为主节点执行mysqld --log-bin开启 binlogmysql-1启动时initContainer 连接mysql-0执行CHANGE MASTER TO MASTER_HOSTmysql-0.mysql-headless并启动复制线程mysql-2同理但 master_host 指向mysql-1形成0→1→2链式复制非环形防脑裂验证复制状态# 进入 mysql-0查看 binlog 位置 kubectl exec -it mysql-0 -n mysql-system -- mysql -uroot -p$(kubectl get secret mysql-root-pass -n mysql-system -o jsonpath{.data.password} | base64 -d) -e SHOW MASTER STATUS\G # 进入 mysql-1查看 Slave_IO_Running 和 Slave_SQL_Running kubectl exec -it mysql-1 -n mysql-system -- mysql -uroot -p$(...) -e SHOW SLAVE STATUS\G | grep -E (IO|SQL)_Running # 应输出Slave_IO_Running: YesSlave_SQL_Running: Yes4. 避坑指南5 条踩过才懂的离线部署铁律离线环境放大了所有微小配置偏差。这 5 条不是理论是我在某金融客户现场连续 3 天熬夜后记下的操作守则每一条都对应一个让整个集群停摆 2 小时以上的事故。4.1 现象kubectl get pod显示mysql-0一直ContainerCreatingdescribe显示FailedMount原因PVC 绑定的 PV 使用了hostPath类型但离线包部署在多节点集群hostPath只在单节点有效mysql-1调度到其他节点时找不到/mnt/data目录。解决立即删除 PVC 和 PV改用local-path或nfs-clientStorageClass。离线包./docs/STORAGE_GUIDE.md有详细适配步骤重点修改./manifests/mysql-statefulset.yaml中volumeClaimTemplates.spec.storageClassName字段。4.2 现象mysql-0进入Running但kubectl logs mysql-0只有Starting MySQL无后续日志原因my.cnf中bind-address 127.0.0.1未改为0.0.0.0导致 mysqld 只监听 localhostKubernetes Service 无法转发流量。解决编辑./configs/my.cnf找到[mysqld]段添加bind-address 0.0.0.0。然后kubectl delete pod mysql-0 -n mysql-system强制重建。4.3 现象mysql-1启动后SHOW SLAVE STATUS显示Seconds_Behind_Master: NULLSlave_SQL_Running: No原因mysql-0的 binlog 文件名在mysql-1初始化时被轮转如mysql-bin.000001→mysql-bin.000002CHANGE MASTER TO指向的旧文件已不存在。解决进入mysql-1执行STOP SLAVE; RESET SLAVE; START SLAVE;。离线包./scripts/fix-replication.sh一键修复此问题。4.4 现象kubectl exec -it mysql-0 -- mysql -uroot -p... -e CREATE DATABASE test;成功但应用连接时报Access denied for user root10.244.1.5原因MySQL 5.7 默认只创建rootlocalhost未授权root%。离线包的 initContainer 脚本中有一行被注释掉了GRANT ALL ON *.* TO root% IDENTIFIED BY xxx;解决编辑./scripts/init-mysql.sh取消该行注释重新kubectl apply -f ./manifests/mysql-statefulset.yaml。4.5 现象mysql-0运行 2 小时后突然OOMKilleddescribe pod显示Memory limit reached原因my.cnf中innodb_buffer_pool_size设为2G但容器内存 limit 仅1.5GiKubernetes OOM Killer 强制终止。解决检查./manifests/mysql-statefulset.yaml中 containers.resources.limits.memory确保 ≥innodb_buffer_pool_size256Mi预留系统开销。离线包默认设为2Gi若集群资源紧张需同步下调my.cnf中的 buffer_pool。5. 验证 MySQL 集群是否真正就绪用这 4 个命令终结所有怀疑装完不是终点验证才是。我从不信任kubectl get pod的绿色Running因为 MySQL 进程可能活着但服务根本不可用。以下 4 个命令每个都直击一个关键维度缺一不可。5.1 连通性验证穿透 Service 层确认负载均衡生效# 创建临时 busybox Pod模拟外部应用连接 kubectl run mysql-test -it --rm --imagebusybox:1.35 --restartNever -n mysql-system -- \ sh -c apk add mysql-client mysql -h mysql -P 3306 -uroot -p$(cat /run/secrets/mysql-root-pass/password) -e SELECT VERSION();关键点用mysql -h mysqlService 名而非mysql -h mysql-0Pod 名验证 Service DNS 解析和 ClusterIP 转发apk add mysql-client是为了在 busybox 中安装客户端避免用curl等非 SQL 工具误判若返回5.7.42说明 Service 层通若报Cant connect to MySQL server检查 Service 的spec.selector是否匹配 Pod label离线包默认app: mysql5.2 一致性验证跨节点读写揪出复制延迟黑洞主从架构最怕“写进去读不出来”。用一个简单事务验证# 在主节点mysql-0插入带时间戳的数据 kubectl exec -it mysql-0 -n mysql-system -- mysql -uroot -p$(kubectl get secret mysql-root-pass -n mysql-system -o jsonpath{.data.password} | base64 -d) -e CREATE DATABASE IF NOT EXISTS verify; USE verify; CREATE TABLE IF NOT EXISTS test (id INT PRIMARY KEY, ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP); INSERT INTO test VALUES (1, NOW()); # 立即在从节点mysql-1查询 kubectl exec -it mysql-1 -n mysql-system -- mysql -uroot -p$(...) -e USE verify; SELECT * FROM test; # 应立刻返回1, 2024-06-15 10:30:45时间戳与插入时一致 # 再查 mysql-2二级从库 kubectl exec -it mysql-2 -n mysql-system -- mysql -uroot -p$(...) -e USE verify; SELECT * FROM test; # 时间戳必须与 mysql-1 完全相同证明链式复制无数据丢失如果mysql-1查不到SHOW SLAVE STATUS\G中Seconds_Behind_Master 0说明复制线程卡住如果mysql-1有数据但mysql-2没有说明mysql-1未开启log_slave_updates离线包已默认开启此步是双重保险。5.3 持久化验证删 Pod 后数据还在才算真持久有状态服务的核心价值是数据不丢。强制删除 Pod看数据是否重建# 记录当前数据 kubectl exec -it mysql-0 -n mysql-system -- mysql -uroot -p$(...) -e USE verify; SELECT COUNT(*) FROM test; # 删除 PodStatefulSet 会自动重建 kubectl delete pod mysql-0 -n mysql-system # 等待 mysql-0 重回 Running kubectl wait --forconditionReady pod/mysql-0 -n mysql-system --timeout300s # 重建后立即查数据 kubectl exec -it mysql-0 -n mysql-system -- mysql -uroot -p$(...) -e USE verify; SELECT COUNT(*) FROM test; # 结果必须与删除前完全一致为什么这步不能省曾有个客户离线包里volumeClaimTemplates的accessModes写成[ReadWriteOnce]但集群 StorageClass 实际只支持ReadWriteMany导致重建的 Pod 挂载了新 PV数据全丢。这个验证能 100% 暴露此类配置错位。5.4 故障转移验证模拟主节点宕机看从库能否顶上这才是高可用的终极考验。我们手动杀掉mysql-0观察mysql-1是否自动晋升# 先确认当前主库是 mysql-0 kubectl exec -it mysql-0 -n mysql-system -- mysql -uroot -p$(...) -e SHOW VARIABLES LIKE read_only; | grep read_only # 应输出read_only OFF # 杀掉 mysql-0模拟宕机 kubectl delete pod mysql-0 -n mysql-system # 等待 mysql-1 状态变为 Running 后检查其 read_only 状态 kubectl exec -it mysql-1 -n mysql-system -- mysql -uroot -p$(...) -e SHOW VARIABLES LIKE read_only; | grep read_only # 若仍为 OFF则说明未自动晋升此时需手动执行 # kubectl exec -it mysql-1 -n mysql-system -- mysql -uroot -p$(...) -e STOP SLAVE; SET GLOBAL read_onlyOFF;离线包默认不启用自动故障转移因需额外部署 Orchestrator 或 MHA增加复杂度但提供了./scripts/failover-to.sh mysql-1脚本一键完成STOP SLAVE、RESET MASTER、SET read_onlyOFF三步。真正的生产环境我会在mysql-1上部署一个轻量级探针当检测到mysql-0失联超过 30 秒自动触发此脚本。从那以后我每次交付离线 MySQL 集群都强制走一遍这 4 个验证命令——不是为了炫技是怕哪天某个my.cnf里的skip-name-resolve被误删导致整个科研平台数据库连接池雪崩。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?