简介这份资源面向需要在 Kubernetes 集群中部署 MySQL 的运维与后端开发人员提供一套可直接落地的全量离线部署方案重点解决内网或受限网络环境下无法拉取镜像、依赖缺失的痛点。压缩包共 7 个文件约 280.98MB包含 4 个 yaml 编排文件、2 个 tar 镜像包和 1 个说明文本yaml 覆盖 Deployment、Service、持久卷与存储声明等核心对象tar 包则提供 MySQL 5.7 镜像可通过 load 方式导入本地镜像仓库说明文本用于辅助快速上手。资源已积累 1095 人学习下载说明其在同类场景中具备一定参考价值。读者可借此获得一套结构完整的离线部署模板直接复用或按需调整即可完成 MySQL 5.7 在 Kubernetes 上的部署同时理解镜像导入、存储挂载与服务暴露之间的配合关系减少从零编写编排文件与排查环境问题的时间成本。1. 为什么我宁愿用离线包在 Kubernetes 里跑 MySQL 5.7生产环境里最怕的不是 MySQL 崩而是镜像拉不下来。上个月帮一个做工业物联网的团队排查故障他们的 Kubernetes 集群跑在客户内网节点无法访问 Docker Hub一个kubectl apply之后 Pod 卡在ImagePullBackOff整整两天业务侧 MySQL 连接池全部超时。最后我带着一个 1.2GB 的离线包进机房二十分钟把 MySQL 5.7 主从拉起来问题才算收住。这件事让我更坚定一个判断在私有化交付、信创环境、边缘节点这些场景里kubernetes mysql的组合能不能落地关键不在 YAML 写得多漂亮而在你有没有一套完整的离线镜像与初始化数据包。这份资源就是围绕这个痛点整理的把 MySQL 5.7 的容器镜像、初始化 SQL、配置模板、Kubernetes 编排文件全部打包脱离公网也能在集群里完成部署。它适合三类人一是做私有化交付、经常进内网机房的实施工程师二是想在自己搭的 K8s 集群里练手 MySQL 有状态服务的后端开发三是被docker安装mysql失败折腾过、想换一条更可控路径的运维。下面我按「包是什么 → 怎么用 → 坑在哪」的顺序拆开讲每一步都给出可抄的命令和参数。2. 离线包结构与 MySQL 5.7 镜像的导入逻辑2.1 离线包里到底装了什么拿到一个离线包第一件事不是急着docker load而是先看清目录结构。这类kubernetes mysql离线包通常包含四类东西镜像 tar 包、Kubernetes 编排 YAML、MySQL 初始化脚本、以及一份部署说明。镜像部分一般会有mysql-5.7.tar这样的文件大小在 400MB 到 600MB 之间取决于基础镜像用的是 Oracle Linux 还是 Debian。编排文件里至少要有 StatefulSet、Headless Service、ConfigMap、Secret 四个对象缺一个都会导致有状态服务跑不起来。我一般会先执行tar -tf看一眼包内清单确认没有缺文件再往下走。这一步能避免「导入了镜像却发现没有初始化 SQL」这种返工。初始化脚本通常放在initdb.d目录下命名以数字开头控制执行顺序比如01-schema.sql、02-data.sql。ConfigMap 里放的是my.cnf片段Secret 里放 root 密码。理解这个分层后面改参数才知道该动哪个文件。2.2 镜像导入与 tag 规范离线部署的第一步是把镜像灌进每个节点的容器运行时。如果你的集群用的是 containerd命令和 Docker 略有差别这点很多人第一次会翻车。# 在每一个可能调度到 MySQL 的节点上执行 # Docker 运行时 docker load -i mysql-5.7.tar # containerd 运行时K8s 1.24 默认 ctr -n k8s.io images import mysql-5.7.tar # 确认镜像已存在 docker images | grep mysql # 或 ctr -n k8s.io images ls | grep mysql逻辑说明docker load会把 tar 里的镜像层解压进本地存储ctr -n k8s.io images import是 containerd 的对应操作命名空间必须是k8s.io否则 kubelet 看不到这个镜像。参数上-i指定输入文件-n指定命名空间。导入完成后一定要核对镜像的REPOSITORY:TAG因为 YAML 里image:字段必须和它完全一致差一个字符就是ImagePullBackOff。提示如果集群有多个节点且没有共享镜像仓库必须在每个节点都导入一遍。只导入 master 是没用的Pod 调度到哪个节点哪个节点就得有镜像。2.3 用 StatefulSet 而不是 Deployment 的理由MySQL 是有状态服务数据目录必须稳定绑定到某个 Pod。Deployment 的 Pod 名字是随机的重建后存储卷挂载关系容易乱所以标准做法是用 StatefulSet 配合volumeClaimTemplates。下面是一段精简后的编排片段重点看serviceName和volumeClaimTemplates两处。apiVersion: apps/v1 kind: StatefulSet metadata: name: mysql spec: serviceName: mysql-headless replicas: 1 selector: matchLabels: app: mysql template: metadata: labels: app: mysql spec: containers: - name: mysql image: mysql:5.7 imagePullPolicy: IfNotPresent env: - name: MYSQL_ROOT_PASSWORD valueFrom: secretKeyRef: name: mysql-secret key: root-password ports: - containerPort: 3306 volumeMounts: - name: data mountPath: /var/lib/mysql - name: config mountPath: /etc/mysql/conf.d volumeClaimTemplates: - metadata: name: data spec: accessModes: [ReadWriteOnce] resources: requests: storage: 20Gi逻辑说明serviceName: mysql-headless指向一个 Headless Service它给每个 Pod 提供稳定的 DNS 记录格式是mysql-0.mysql-headless.default.svc.cluster.local。imagePullPolicy: IfNotPresent是离线场景的关键它让 kubelet 优先用本地已有镜像不去远端拉。volumeClaimTemplates会自动为每个副本创建 PVCPod 重建后仍挂回原来的卷。参数上storage: 20Gi按你实际数据量调整mountPath必须是/var/lib/mysql这是 MySQL 5.7 官方镜像的数据目录。2.4 初始化 SQL 的挂载方式很多人把初始化 SQL 直接打进镜像改一次数据就要重新构建镜像这在离线环境里非常痛苦。更合理的做法是用 ConfigMap 挂载到/docker-entrypoint-initdb.d官方镜像启动时会自动执行这个目录下的.sql、.sh、.sql.gz文件。# 从 SQL 文件生成 ConfigMap kubectl create configmap mysql-initdb \ --from-file01-schema.sql \ --from-file02-data.sql \ -n default # 在 StatefulSet 里挂载 # volumeMounts 增加 # - name: initdb # mountPath: /docker-entrypoint-initdb.d # volumes 增加 # - name: initdb # configMap: # name: mysql-initdb逻辑说明--from-file会把每个文件变成一个 key挂载后目录里就是原始文件名。注意初始化脚本只在数据目录为空时执行一次如果 PVC 里已经有数据重启 Pod 不会重跑。参数上文件名前缀数字决定执行顺序01-一定在02-之前。ConfigMap 有 1MB 大小限制数据量大的初始化建议用sql.gz压缩或者改用 Job 单独导入。3. 从零到可连接部署、验证与参数调优3.1 完整部署流程与顺序离线部署最忌讳东一榔头西一棒子顺序错了就会出现「Pod 起来了但连不上」。我一般按下面这个顺序走每一步都有明确的验证点。# 1. 创建命名空间可选便于隔离 kubectl create namespace mysql-offline # 2. 创建 Secret 存放 root 密码 kubectl create secret generic mysql-secret \ --from-literalroot-passwordYourStrongPass123! \ -n mysql-offline # 3. 创建 ConfigMap 存放 my.cnf 与初始化 SQL kubectl create configmap mysql-config \ --from-filemy.cnf \ -n mysql-offline # 4. 创建 Headless Service kubectl apply -f mysql-headless-svc.yaml -n mysql-offline # 5. 创建 StatefulSet kubectl apply -f mysql-statefulset.yaml -n mysql-offline # 6. 观察 Pod 状态 kubectl get pods -n mysql-offline -w逻辑说明Secret 必须在 StatefulSet 之前创建否则 Pod 会因为找不到secretKeyRef而卡在CreateContainerConfigError。Headless Service 要先于 StatefulSet因为serviceName引用它。-w参数让你实时看到 Pod 从Pending到Running的过程如果卡在Pending多半是 PVC 没绑定卡在CrashLoopBackOff多半是配置或权限问题。参数上密码里不要带$、!这类在 YAML 里需要转义的字符能省掉一堆玄学问题。3.2 连接验证与常见连通性排查Pod 变成Running不代表能连。我习惯分三层验证容器内、集群内、集群外。# 第一层进入 Pod 内部验证 MySQL 进程 kubectl exec -it mysql-0 -n mysql-offline -- bash mysql -uroot -p # 输入密码后执行 SHOW DATABASES; SELECT VERSION(); # 第二层用临时 Pod 验证集群内 DNS 与服务发现 kubectl run mysql-client --rm -it --imagemysql:5.7 --restartNever -- \ mysql -h mysql-0.mysql-headless.mysql-offline.svc.cluster.local -uroot -p # 第三层端口转发到本地验证 kubectl port-forward mysql-0 3306:3306 -n mysql-offline # 另开终端 mysql -h127.0.0.1 -P3306 -uroot -p逻辑说明第一层确认 MySQL 进程本身正常SELECT VERSION()应该返回5.7.x。第二层验证 Headless Service 的 DNS 解析主机名格式是pod-name.service-name.namespace.svc.cluster.local这是 StatefulSet 的标准寻址方式。第三层用port-forward把 Pod 端口映射到本地适合用 Navicat、DBeaver 这类图形工具连。参数上-h后面跟主机名或 IP-P是大写小写-p是密码提示这两个别搞混。3.3 my.cnf 关键参数怎么调MySQL 5.7 默认配置是给 1GB 内存的小机器用的放到 K8s 里如果不调性能会很差。下面这份my.cnf片段是我在 4 核 8G 的 Pod 上常用的配置通过 ConfigMap 挂载到/etc/mysql/conf.d。[mysqld] # 字符集避免中文乱码 character-set-serverutf8mb4 collation-serverutf8mb4_unicode_ci # InnoDB 缓冲池建议设为容器内存的 50%-70% innodb_buffer_pool_size4G # 连接数按业务并发调整 max_connections500 # 日志与慢查询 slow_query_log1 long_query_time2 slow_query_log_file/var/lib/mysql/slow.log # 时区 default-time-zone08:00 # 关闭 DNS 反查加快连接 skip-name-resolve逻辑说明innodb_buffer_pool_size是最影响性能的参数设太小会频繁读磁盘设太大可能触发 OOM。容器里要留出内存给系统和其他进程所以 8G 的 Pod 给 4G 比较稳。skip-name-resolve能避免每次连接做反向 DNS 查询在 K8s 环境里尤其明显。default-time-zone不设的话容器默认 UTC业务写入的时间会差 8 小时这个坑我见过太多次。参数改完要重启 Pod 生效kubectl delete pod mysql-0让它重建即可PVC 数据不会丢。3.4 数据持久化与备份策略离线环境里数据丢了没人能帮你恢复所以备份必须做在部署阶段而不是出事之后。StatefulSet 的 PVC 保证了 Pod 重建数据不丢但 PVC 本身也可能被误删所以还要有逻辑备份。# 逻辑备份导出到宿主机 kubectl exec mysql-0 -n mysql-offline -- \ mysqldump -uroot -pYourStrongPass123! --all-databases --single-transaction \ /backup/mysql-$(date %Y%m%d).sql # 定时备份用 CronJob apiVersion: batch/v1 kind: CronJob metadata: name: mysql-backup spec: schedule: 0 2 * * * jobTemplate: spec: template: spec: containers: - name: backup image: mysql:5.7 command: - bash - -c - mysqldump -hmysql-0.mysql-headless -uroot -p$MYSQL_PWD --all-databases --single-transaction /backup/db-$(date %Y%m%d).sql env: - name: MYSQL_PWD valueFrom: secretKeyRef: name: mysql-secret key: root-password restartPolicy: OnFailure逻辑说明--single-transaction让 InnoDB 表在不锁表的情况下导出一致性快照生产环境必加。MYSQL_PWD环境变量传密码比命令行-p更安全不会出现在ps输出里。CronJob 的schedule用标准 cron 表达式0 2 * * *表示每天凌晨两点。备份文件要落到 PVC 或宿主机目录别只留在容器里容器一删就没了。4. 避坑与排查离线部署 MySQL 5.7 的五个血泪教训4.1 Pod 一直 PendingPVC 绑不上现象kubectl get pods显示Pendingkubectl describe pod里事件提示no persistent volumes available for this claim。原因通常是集群没有默认 StorageClass或者volumeClaimTemplates里指定的storageClassName不存在。解决方式是先kubectl get storageclass看有没有可用存储类没有就装一个本地卷 provisioner或者手动创建 PV 并在 PVC 里用volumeName绑定。离线环境里很多人忘了装存储插件这是最高频的坑。4.2 镜像导入后仍然 ImagePullBackOff现象明明docker images能看到mysql:5.7Pod 还是报ImagePullBackOff。原因有两个一是节点用的 containerd 而不是 Docker镜像导进了 Docker 的存储kubelet 看不到二是 YAML 里写的 tag 和导入的 tag 不一致比如导入的是mysql:5.7.44YAML 写的是mysql:5.7。解决方式是确认节点运行时用对应的ctr命令导入并且docker images和 YAML 里的image字段逐字符核对。imagePullPolicy设成IfNotPresent或Never也能减少误判。4.3 初始化 SQL 没执行现象Pod 起来了但SHOW DATABASES里没有初始化脚本里建的库。原因是/docker-entrypoint-initdb.d只在数据目录为空时执行如果 PVC 是复用的旧卷里面已经有数据脚本就被跳过。解决方式是确认 PVC 是新建的或者手动进容器执行 SQL。另一个可能是 ConfigMap 挂载权限问题脚本没有可读权限检查mountPath和文件名后缀是否为.sql、.sh、.sql.gz之一。4.4 连接报 SSL 错误现象客户端连接时报SSL connection error或ERROR 2026。MySQL 5.7 官方镜像默认开启了 SSL但有些老客户端或驱动不兼容。原因可能是客户端不支持服务端要求的 TLS 版本或者证书路径不对。解决方式是在my.cnf里加ssl0临时关闭或者客户端连接串加useSSLfalse。生产环境建议保留 SSL升级客户端驱动到支持 TLS 1.2 的版本。这个坑在mysql ssl连接错误的搜索里出现频率很高。4.5 时区差 8 小时导致数据错乱现象业务写入的时间比实际时间早 8 小时或者定时任务在错误的时间触发。原因是容器默认时区是 UTCMySQL 继承了系统时区。解决方式是在my.cnf里设default-time-zone08:00同时在 StatefulSet 的 env 里加TZAsia/Shanghai。两个地方都设才保险只设一个可能被覆盖。改完重启 Pod用SELECT NOW();验证。5. 进阶技巧把离线包做成可复用的交付模板5.1 用 Helm 把编排参数化每次交付都手改 YAML 太累我后来把整套编排抽成 Helm Chart离线包里带上 Chart 和镜像 tar到现场helm install一条命令搞定。核心是把密码、存储大小、副本数、镜像 tag 抽成values.yaml里的变量。# values.yaml image: repository: mysql tag: 5.7 pullPolicy: IfNotPresent auth: rootPassword: ChangeMe123! persistence: size: 20Gi storageClassName: local-path resources: limits: memory: 8Gi cpu: 4逻辑说明values.yaml是 Helm 的默认参数文件helm install mysql ./chart -f custom-values.yaml可以覆盖。pullPolicy在离线场景固定IfNotPresent。storageClassName按现场实际存储插件填。这样一份 Chart 可以在不同客户环境复用只改 values 文件不用碰模板。参数上rootPassword建议用--set在命令行传避免明文落在文件里。5.2 验证清单交付前必须走一遍部署完别急着交我习惯按下面这张表逐项确认任何一项不过都不算完成。检查项命令预期结果Pod 状态kubectl get podsRunning重启次数 0PVC 绑定kubectl get pvcBound服务发现nslookup mysql-0.mysql-headless解析到 Pod IP版本确认SELECT VERSION();5.7.x字符集SHOW VARIABLES LIKE character%;utf8mb4时区SELECT NOW();与本地时间一致初始化数据SHOW DATABASES;包含业务库备份任务kubectl get cronjob存在且 schedule 正确这张表看着简单但每次交付我都会走一遍因为漏掉任何一项到了客户那边出问题排查成本是现场的好几倍。尤其是时区和字符集属于「不报错但数据不对」的隐性坑事后修复比重装还麻烦。5.3 一个具体技巧用 initContainer 做数据目录权限修复离线环境里 PV 的权限经常不对MySQL 启动时报Permission denied写不了/var/lib/mysql。与其手动chown不如在 StatefulSet 里加一个 initContainer 自动修。initContainers: - name: fix-permissions image: busybox command: [sh, -c, chown -R 999:999 /var/lib/mysql] volumeMounts: - name: data mountPath: /var/lib/mysql securityContext: runAsUser: 0逻辑说明MySQL 5.7 官方镜像里 mysql 用户的 UID 是 999数据目录必须属于这个用户。initContainer 以 root 身份运行先于主容器执行把目录属主改对。runAsUser: 0是必须的否则 busybox 没权限改。这个技巧帮我省掉了无数次进节点手动chown的操作尤其是在用 NFS 或本地卷的时候。从那以后我每次交付离线 MySQL都会先把 initContainer 和验证清单过一遍再让业务方连。这套流程跑顺了内网部署从半天缩短到二十分钟。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?