简介本资源是一套面向Kubernetes初学者与运维工程师的MySQL 5.7全量离线部署方案专为无外网环境或需快速落地生产级数据库服务的场景设计解决容器化MySQL部署中镜像拉取失败、YAML配置繁琐、数据初始化困难等典型问题。压缩包共7个文件4个核心YAML、2个MySQL 5.7/8.0离线tar镜像、1份启动说明txt总大小280.98MB其中deployment、service、pv与pvc四类YAML已预调通支持直接kubectl apply部署配合load方式导入预置数据免去手动构建镜像与编写存储卷逻辑的重复劳动。目前已有1095人学习下载资源结构简洁聚焦——所有YAML即配即用tar包含完整MySQL二进制分发版txt文档明确标注启动顺序与关键参数说明适合快速验证K8s有状态服务编排流程、理解PV/PVC持久化机制及离线环境下的数据库交付范式。1. 为什么 Kubernetes 部署 MySQL 5.7 必须用全量离线包——内网交付、金融审计、军工产线的真实约束你不是在“学 Kubernetes”而是在给银行核心账务系统补一个灾备库不是在“试玩 Docker”而是在航天院所的涉密内网里连 USB 都被物理封禁的工控环境里把 MySQL 5.7.44 装进 K8s 集群——此时kubectl apply -f mysql.yaml会卡在ImagePullBackOff因为镜像仓库地址根本解析不了 DNShelm repo add bitnami https://charts.bitnami.com/bitnami这行命令连https://都打不开。所谓“全量离线包”不是 zip 压缩包而是包含MySQL 5.7 官方二进制 tar 包 适配 Kubernetes 的 StatefulSet YAML 模板 初始化 SQL 脚本 预编译 Helm Chart 所有依赖镜像的 tar 归档的交付单元。它解决的不是“能不能跑”而是“审计能否过”镜像哈希可验、SQL 脚本可审、启动参数可追溯、无外网调用痕迹。适合对象很明确等保三级以上系统运维、信创替代项目实施工程师、军工/电力/轨交行业私有云交付团队。如果你还在用docker pull mysql:5.7再docker save那离真正交付还差三道防火墙和两次安全扫描。2. 全量离线包的四大组成模块从镜像到初始化脚本的闭环设计全量离线包不是把一堆文件塞进 ZIP 就完事。它必须形成“镜像拉取 → 存储挂载 → 初始化 → 健康检查 → 权限加固”的完整闭环。我一般按四个模块组织目录结构每个模块都对应一次交付验收点mysql57-offline-k8s/ ├── images/ # 所有必需镜像的 tar 归档含基础镜像 │ ├── mysql-5.7.44.tar # 官方 mysql:5.7.44 镜像非官方镜像见避坑章 │ ├── busybox-1.35.0.tar # initContainer 用 │ └── alpine-3.18.5.tar # sidecar 或 backup 工具基础镜像 ├── charts/ # Helm Chart已渲染或可离线 render │ └── mysql57-statefulset/ # 自研 Chart不含任何 externalRepo 依赖 ├── configs/ # 可审计的配置项 │ ├── my.cnf.tmpl # 模板化配置支持 value 注入 │ └── init-sql/ # 分库分表前必须执行的初始化脚本含字符集、时区、密码策略 ├── scripts/ # 离线环境专用工具链 │ ├── load-images.sh # 批量 load 镜像到所有 worker 节点 │ └── verify-checksum.sh # 校验 tar 包完整性SHA256 GPG 签名 └── README.md # 交付清单镜像 SHA256、SQL 脚本版本、Helm values.yaml 关键字段说明提示不要用docker export或docker commit生成镜像。MySQL 5.7 官方镜像mysql:5.7.44是唯一被 Oracle 官方签名、且被等保测评机构认可的发行版。任何基于centos:7自建的镜像在金融客户现场会被直接拒收——他们要的是sha256:9a15...这串值能在 Docker Hub 官方页面 上查到对应 digest。2.1 镜像层为什么必须用docker pull mysql:5.7.44而不是mysql:5.7mysql:5.7是 latest tag每次 pull 可能拉到不同 digest比如 5.7.43 或 5.7.45违反“一次构建、处处运行”的交付原则。真实项目中我们锁定5.7.44是因为它是 MySQL 5.7 系列最后一个 GA 版本2023-10-16 发布修复了 CVE-2023-21908权限绕过和 CVE-2023-21912内存泄漏且被多数国产中间件兼容测试报告引用。# 在有外网的跳板机上执行非生产环境 docker pull mysql:5.7.44 docker save mysql:5.7.44 -o images/mysql-5.7.44.tar # 验证 digest 是否与官网一致 docker inspect mysql:5.7.44 --format{{.Id}} | cut -d: -f2 | head -c32 # 输出应为9a15b4e7b8c9d0e1f2a3b4c5d6e7f8a9 实际以官网为准该命令输出的 ID 前 32 位必须与 Docker Hub 页面 中显示的Digest完全一致。这是审计必查项。2.2 StatefulSet 模板为什么不用 DeploymentPersistentVolumeClaim 如何绑定到具体存储类MySQL 是有状态服务必须用StatefulSet而非Deployment。关键区别在于StatefulSet保证 Pod 名称固定mysql-0,mysql-1便于 DNS 解析mysql-0.mysql-headless.default.svc.cluster.localvolumeClaimTemplates自动生成 PVC并与 Pod 生命周期绑定删除 Pod 不删数据启动顺序严格mysql-0启动成功后才启mysql-1避免主从脑裂。以下是mysql57-offline-k8s/charts/mysql57-statefulset/templates/statefulset.yaml中最易错的三处配置# 注意storageClassName 必须与目标集群实际存在的 StorageClass 名称完全一致 # 不能写成 nfs 或 local-path而应是客户环境中 kubectl get sc 真实返回的名称 volumeClaimTemplates: - metadata: name: data spec: accessModes: [ReadWriteOnce] storageClassName: prod-nfs-sc # ← 必须提前确认否则 PVC 一直 Pending resources: requests: storage: 50Gi # initContainers 必须校验 /var/lib/mysql 是否为空否则初始化脚本会重复执行 initContainers: - name: init-mysql image: busybox:1.35.0 command: [sh, -c, if [ ! -d /var/lib/mysql/mysql ]; then echo Empty dir, ready for init; else echo Data exists, skip init; exit 0; fi] volumeMounts: - name: data mountPath: /var/lib/mysql # livenessProbe 和 readinessProbe 必须用 TCP 探针而非 exec # 因为 exec 依赖 mysql client而 client 不一定在镜像里官方镜像没装 livenessProbe: tcpSocket: port: 3306 initialDelaySeconds: 30 periodSeconds: 10 readinessProbe: tcpSocket: port: 3306 initialDelaySeconds: 15 periodSeconds: 5storageClassName是最大雷区。很多交付团队在测试环境用local-path到客户现场才发现客户用的是huawei-csi-nas或tke-cbs。必须在交付前拿到客户kubectl get sc输出把storageClassName字段硬编码进模板而不是留成{{ .Values.storageClassName }}—— 因为离线包里不能有未赋值的变量。3. 初始化脚本与权限加固5.7 特有的sql_mode和validate_password处理MySQL 5.7 默认开启validate_password插件且sql_mode包含STRICT_TRANS_TABLES这会导致很多老业务 SQL 直接报错如INSERT INTO t VALUES (NULL)。离线包里的初始化脚本必须显式关闭或降级这些限制但又不能牺牲安全性——这是平衡点。3.1my.cnf.tmpl中必须覆盖的 5 个关键参数mysql57-offline-k8s/configs/my.cnf.tmpl不是简单复制/etc/my.cnf而是专为容器化定制的最小可行配置。以下 5 项是金融客户验收必查项参数推荐值为什么必须设character-set-serverutf8mb4避免 emoji 存储乱码5.7 默认是latin1collation-serverutf8mb4_unicode_ci与字符集强绑定不设则用默认latin1_swedish_cidefault-time-zone08:00容器默认 UTC不设会导致NOW()返回错误时间sql_modeNO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES保留 STRICT审计要求但去掉ONLY_FULL_GROUP_BY老系统兼容validate_password_policyLOW审计允许最低强度但必须显式设否则用默认MEDIUM密码需含大小写数字符号# mysql57-offline-k8s/configs/my.cnf.tmpl [mysqld] skip-host-cache skip-name-resolve user mysql port 3306 socket /var/run/mysqld/mysqld.sock pid-file /var/run/mysqld/mysqld.pid datadir /var/lib/mysql log-error /var/log/mysql/error.log bind-address 0.0.0.0 max_connections 200 table_open_cache 2000 sort_buffer_size 2M read_buffer_size 1M read_rnd_buffer_size 1M net_buffer_length 16K thread_stack 192K server-id {{ .Values.serverId }} character-set-server utf8mb4 collation-server utf8mb4_unicode_ci default-time-zone 08:00 sql_mode NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES validate_password_policy LOW注意server-id是模板变量由 Helm values.yaml 注入用于主从复制场景。单实例部署也必须设如1否则SHOW MASTER STATUS报错。3.2init-sql/01-base.sql绕过validate_password的血泪经验validate_password插件在mysqld --initialize后自动启用但初始化脚本在mysqld启动后才执行。此时 root 密码已生成但插件强制校验新密码强度。解决方案在初始化 SQL 中先禁用插件再改密码最后重载插件。-- mysql57-offline-k8s/configs/init-sql/01-base.sql -- Step 1: Disable validate_password plugin to allow weak password UNINSTALL PLUGIN validate_password; -- Step 2: Set root password (use mysql_native_password for 5.7 compatibility) ALTER USER root% IDENTIFIED WITH mysql_native_password BY MyPssw0rd123; -- Step 3: Reinstall plugin with LOW policy INSTALL PLUGIN validate_password SONAME validate_password.so; SET GLOBAL validate_password_policy LOW; SET GLOBAL validate_password_length 8; SET GLOBAL validate_password_mixed_case_count 1; SET GLOBAL validate_password_number_count 1; SET GLOBAL validate_password_special_char_count 1; -- Step 4: Create app user with minimal privileges CREATE USER appuser% IDENTIFIED BY AppPss2024; GRANT SELECT, INSERT, UPDATE, DELETE ON *.* TO appuser%; FLUSH PRIVILEGES;注意ALTER USER ... IDENTIFIED WITH mysql_native_password是关键。MySQL 5.7 默认用caching_sha2_password但很多 Java JDBC 驱动如 mysql-connector-java 5.1.x不支持连接会报Client does not support authentication protocol requested by server。必须显式指定mysql_native_password。4. 镜像加载与 Helm 渲染离线环境下的三步落地法离线环境没有helm repo add也没有kubectl create ns的网络请求。整个流程必须拆成三个原子操作每步可独立验证、可回滚。4.1load-images.sh如何确保所有节点镜像一致load-images.sh不是简单docker load它必须检查目标节点是否安装 docker校验 tar 包完整性SHA256并行加载避免单节点耗时过长记录每台节点加载结果。#!/bin/bash # mysql57-offline-k8s/scripts/load-images.sh set -e IMAGES_DIR../images NODES(node1 node2 node3) # 替换为客户实际节点名 IMAGE_LIST(mysql-5.7.44.tar busybox-1.35.0.tar alpine-3.18.5.tar) # Step 1: Verify checksum echo Verifying image checksums for img in ${IMAGE_LIST[]}; do if [ ! -f $IMAGES_DIR/$img ]; then echo ERROR: $img not found in $IMAGES_DIR exit 1 fi # 使用预置的 checksum 文件由交付方提供 if ! sha256sum -c $IMAGES_DIR/$img.sha256 --status; then echo ERROR: Checksum failed for $img exit 1 fi done # Step 2: Load to all nodes via ssh echo Loading images to nodes for node in ${NODES[]}; do echo Loading to $node... for img in ${IMAGE_LIST[]}; do scp $IMAGES_DIR/$img $node:/tmp/ ssh $node docker load -i /tmp/$img rm -f /tmp/$img done done echo ✅ All images loaded successfully该脚本依赖客户提前配置好免密 SSHssh-copy-id这是交付标准动作。若客户禁用 SSH改用kubectl cp上传到某 Pod 再docker load但效率极低仅作备用方案。4.2 Helm Chart 离线渲染为什么不用helm install而用helm templatehelm install需要 tillerv2或 kubeconfig 权限v3且会创建Secret类型 release 记录增加审计复杂度。离线包采用helm template渲染为纯 YAML再kubectl apply全程无状态、可审计、可 diff。# 在跳板机有 helm CLI上执行 helm template \ --name-template mysql-prod \ --namespace mysql-prod \ --set serverId1 \ --set mysqlRootPasswordMyPssw0rd123 \ --set storageClassNameprod-nfs-sc \ --set persistence.size50Gi \ ./charts/mysql57-statefulset ./rendered/mysql-prod.yaml # 交付时只给 ./rendered/mysql-prod.yaml不给 helm CLI生成的mysql-prod.yaml必须人工检查三处spec.template.spec.containers[0].env中MYSQL_ROOT_PASSWORD是否 base64 编码K8s Secret 要求volumeClaimTemplates[0].spec.storageClassName是否为客户真实 SC 名initContainers[0].command中路径是否与镜像内路径一致官方镜像中 MySQL 数据目录是/var/lib/mysql不是/data。5. 避坑指南交付现场最常翻车的 5 个问题与解法交付不是“跑通就行”而是“一次过审”。以下是我在 12 个金融/军工项目中踩过的真坑按发生频率排序5.1 现象PVC 一直处于Pending状态kubectl describe pvc显示no persistent volumes available for this claim原因客户集群未创建StorageClass或storageClassName字段值与kubectl get sc输出不匹配常见大小写错误如PROD-NFS-SCvsprod-nfs-sc解决提前索要客户kubectl get sc -o wide输出将storageClassName硬编码进模板若客户无 SC需提供local-volume-provisioner部署 YAML离线包中已备好addons/local-volume-provisioner.yaml5.2 现象Pod 启动后立即 CrashLoopBackOffkubectl logs mysql-0显示Cant start server : Bind on unix socket: Permission denied原因官方 MySQL 镜像要求/var/lib/mysql目录属主为mysql:mysqlUID 999:999但某些 NFS 存储卷挂载后属主为root:root且fsGroup设置无效解决在 StatefulSet 中显式设置securityContextsecurityContext: runAsUser: 999 runAsGroup: 999 fsGroup: 999并确保 PVC 所在存储支持fsGroupNFSv4 支持NFSv3 不支持5.3 现象kubectl exec -it mysql-0 -- mysql -uroot -p输入密码后报Access denied for user rootlocalhost原因MySQL 5.7 初始化时生成两个 root 用户rootlocalhost和root%但mysql -uroot -p默认连接localhost而初始化 SQL 只改了root%的密码解决初始化 SQL 中必须同时处理两个用户ALTER USER rootlocalhost IDENTIFIED BY MyPssw0rd123; ALTER USER root% IDENTIFIED BY MyPssw0rd123;5.4 现象SELECT NOW()返回 UTC 时间而非东八区时间原因容器内未设TZ环境变量且my.cnf中default-time-zone未生效MySQL 5.7 需重启生效但 StatefulSet 中configMap更新不会触发重启解决在 StatefulSet 中注入环境变量env: - name: TZ value: Asia/Shanghai并确保my.cnf中default-time-zone 08:00已配置双重保险5.5 现象helm template生成的 YAML 中imagePullPolicy: Always导致离线环境拉不到镜像原因Helm Chart 中未显式设imagePullPolicy: IfNotPresent而 Helm 默认值为Always解决在values.yaml中强制指定image: repository: mysql tag: 5.7.44 pullPolicy: IfNotPresent # ← 必须显式设6. 验证 checklist 与交付签字单让客户签字前就确认所有审计项交付不是扔个包就走。我坚持用一张 12 项的《MySQL 5.7 Kubernetes 离线部署验证签字单》客户运维、DBA、安全三方签字后才算完成。这张表不是技术文档而是责任界面——哪项没过谁负责整改。序号验证项方法交付物位置客户签字1镜像 digest 与 Docker Hub 官网一致docker inspect mysql:5.7.44 --format{{.Id}}对比官网images/README.md□2PVC 绑定成功kubectl get pvc显示Boundkubectl get pvc -n mysql-prodrendered/mysql-prod.yaml□3Pod 运行且READY 1/1kubectl get pod -n mysql-prod同上□4mysql -h mysql-prod.default.svc.cluster.local -uroot -p可登录提供测试脚本test-connect.shscripts/□5SELECT sql_mode返回含STRICT_TRANS_TABLES且不含ONLY_FULL_GROUP_BY执行 SQLconfigs/init-sql/01-base.sql□6SELECT character_set_server返回utf8mb4执行 SQLconfigs/my.cnf.tmpl□7SELECT time_zone返回08:00执行 SQLconfigs/my.cnf.tmpl□8SHOW VARIABLES LIKE validate_password%;显示LOW策略执行 SQLconfigs/init-sql/01-base.sql□9SELECT User,Host FROM mysql.user WHERE Userappuser;返回appuser %执行 SQLconfigs/init-sql/01-base.sql□10kubectl get secret mysql-prod -o jsonpath{.data.mysql-root-password}base64 -d解密值与values.yaml 一致命令验证charts/mysql57-statefulset/values.yaml11kubectl get events -n mysql-prod --field-selector typeWarning无 Warning 事件命令验证无□12kubectl get pod mysql-0 -o yamlgrep -A5 securityContext显示runAsUser: 999命令验证charts/mysql57-statefulset/templates/statefulset.yaml最后一项我永远手写加一句“已确认无外网依赖所有镜像、脚本、配置均来自离线包未执行任何curl/wget/helm repo操作。”——这不是客套话是给客户安全部门的承诺书。交付不是终点而是下一次升级的起点。我习惯在离线包根目录放一个UPGRADE-NOTES.md里面只写三行下次升级 MySQL 5.7.x → 5.7.y只需替换images/mysql-5.7.y.tar和更新values.yaml中tag字段主从切换脚本已内置路径scripts/failover.sh但需 DBA 提前配置server-id和replicate-do-db所有 SQL 脚本均通过mysql --print-defaults验证语法无需额外客户端。希望帮到你。本文还有配套的精品资源点击获取