03 K8s 部署 MySQL + Redis + Nginx:持久化、Secret 与服务暴露实战
03 K8s 部署 MySQL Redis Nginx持久化、Secret 与服务暴露实战摘要在上一篇文章搭好的 K3s 集群上用 YAML 编排 MySQL带 PV/PVC 持久化、RedisAOF PVC和 NginxConfigMap 注入页面 多副本密码全部走 Secret 管理并完成删 Pod 数据不丢的持久化验证与公网访问验证。一、背景与目标K8s 上跑有状态应用数据库和静态服务Web必须回答三个问题数据存哪容器是临时的Pod 一删数据就没了、密码放哪YAML 里写明文是安全事故、外面怎么访问Pod 有独立 IP 且随时变化。本文用 MySQL Redis Nginx 三个经典应用把答案逐个落地。二、环境说明项目值集群K3s v1.36.41 Server 2 Agent存储local-path StorageClassK3s 内置动态供给镜像mysql:5.7 / redis:7-alpine / nginx:1.27-alpine命名空间app三、架构设计集群内部ns:app:30080:30080:30006:30007挂载挂载MySQLPVC 5Gi SecretRedisPVC 1Gi SecretNginx Pod1Nginx Pod2InternetConfigMap nginx-index持久化PVCPersistentVolumeClaim持久化卷声明绑定 local-path StorageClass数据落在节点磁盘Pod 删除重建后自动挂回同一份数据。密码管理Secret 对象保存密码Pod 通过secretKeyRef注入环境变量YAML 里不出现明文。服务暴露ClusterIP 供集群内部互访NodePort 类型把端口映射到节点公网可直接访问。四、实操步骤4.1 密码安全模板占位符 运行时注入仓库中的 YAML 是模板密码用占位符apiVersion:v1kind:Secretmetadata:name:mysql-secretnamespace:apptype:OpaquestringData:root-password:{{MYSQL_ROOT_PASSWORD}}# 占位符, 运行时替换app-user:loomyapp-password:{{MYSQL_APP_PASSWORD}}部署脚本用secrets.token_urlsafe(12)生成随机强密码并渲染后 apply仓库与日志中零明文凭据subs{{{MYSQL_ROOT_PASSWORD}}:secrets.token_urlsafe(12),...}texttext.replace(k,v)# 渲染 - 上传 - apply - 删除临时文件4.2 MySQLStateful 语义的 Deployment PVCapiVersion:v1kind:PersistentVolumeClaimmetadata:name:mysql-pvcnamespace:appspec:accessModes:[ReadWriteOnce]storageClassName:local-path# 动态供给resources:requests:{storage:5Gi}---apiVersion:apps/v1kind:Deploymentspec:...containers:-name:mysqlimage:docker.io/library/mysql:5.7env:-{name:MYSQL_ROOT_PASSWORD,valueFrom:{secretKeyRef:{name:mysql-secret,key:root-password}}}volumeMounts:-{name:data,mountPath:/var/lib/mysql}volumes:-name:datapersistentVolumeClaim:{claimName:mysql-pvc}4.3 RedisAOF 持久化 密码command:[redis-server,--requirepass,$(REDIS_PASS),--appendonly,yes]env:-{name:REDIS_PASS,valueFrom:{secretKeyRef:{name:redis-secret,key:requirepass}}}4.4 NginxConfigMap 注入页面 多副本 NodePort页面内容放 ConfigMapPod 只挂载不重新构建镜像$(POD_NAME)等通过 downwardAPI 注入显示 Pod 身份apiVersion:v1kind:ConfigMapmetadata:name:nginx-indexdata:index.html:|h1 Loomy K8s 集群欢迎页/h1 p classnodePod: $(POD_NAME) | Node: $(NODE_NAME)/p---# Deployment replicas: 2 ServiceapiVersion:v1kind:Servicespec:selector:{app:nginx}ports:-{port:80,targetPort:80,nodePort:30080}type:NodePort4.5 部署与状态$ kubectl apply -f mysql.yaml -f redis.yaml -f nginx.yaml $ kubectl get pods,svc -n app -o wide pod/mysql-57b56f8d55-9vjgm 1/1 Running 10.42.2.3 ecs-5c2d-6542-0003 pod/nginx-7dccf9fc47-pbtxc 1/1 Running 10.42.0.10 ecs-5c2d-6542-0001 pod/nginx-7dccf9fc47-wr8j7 1/1 Running 10.42.1.3 ecs-5c2d-6542-0002 pod/redis-7d794cd7bd-fh6gt 1/1 Running 10.42.1.4 ecs-5c2d-6542-0002 service/nginx NodePort 80:30080/TCP service/mysql-nodeport NodePort 3306:30006/TCP service/redis-nodeport NodePort 6379:30007/TCP五、效果验证5.1 公网访问 Nginx$ curl http://120.46.211.233:30080/ HTTP 200, 含欢迎页标记: True # 内网访问 0.4ms, 公网同样 200浏览器直接打开http://120.46.211.233:30080/即可看到Loomy K8s 集群欢迎页。5.2 MySQL 持久化验证删 Pod 数据不丢1) 写入测试数据 mysql CREATE TABLE t (id INT PRIMARY KEY AUTO_INCREMENT, v VARCHAR(64)); mysql INSERT INTO t(v) VALUES (k8s-persist-ok); mysql SELECT * FROM t; -------------------- | id | v | -------------------- | 1 | k8s-persist-ok | -------------------- 2) 强制删除 Pod模拟故障 $ kubectl delete pod mysql-57b56f8d55-svfh7 -n app pod mysql-57b56f8d55-svfh7 deleted 3) 新 Pod 自动拉起后查数据 $ kubectl exec -it mysql-57b56f8d55-bsxn5 -- mysql -uroot -p****** -e USE persist_test; SELECT * FROM t; -------------------- | id | v | -------------------- | 1 | k8s-persist-ok | ← 数据还在! --------------------持久化验证通过PVC 让数据活在 Pod 之外Pod 生死不影响数据。5.3 Redis 连通验证$ redis-cli -a ****** ping PONG六、踩坑与解决方案坑 1MySQL 官方镜像的密码只在首次初始化生效现象改了 Secret 密码并rollout restartPod 起来了但Access denied for user root。原因MySQL 镜像启动脚本只有在/var/lib/mysql 为空首次初始化时才读取MYSQL_ROOT_PASSWORD并固化到数据目录数据目录一旦初始化之后每次启动都用数据目录里存的密码环境变量被忽略。解决想换密码必须重新初始化数据删 Deployment PVC Secret 后用新密码重建或初始化后手动ALTER USER。坑 2f-string 与 kubectl jsonpath 的转义陷阱现象kubectl get secret ... -o jsonpath{{.data.root-password}}在 Python f-string 里取不到密码得到一坨报错文本。原因f-string只对字面量里的{{转义运行时拼接进替换字段的值不会再转义。而 kubectl 的 jsonpath 语法恰恰是单花括号{.data.root-password}双花括号反而报unrecognized character in action: U007B。解决jsonpath 统一用单花括号写法拼接时避免二次转义见scripts/verify_apps.py。坑 3测试脚本 f-string 里写 curl 的%{http_code}现象fcurl -w %{http_code}直接NameError: name http_code is not defined。原因%{http_code}被 Python 当成格式化占位符。解决这类命令改成普通字符串不带 f 前缀或把%写成%%。七、总结与延伸持久化三件套PVC 声明容量 → StorageClass 动态供给 → 应用挂载数据卷验证手段就是删 Pod 看数据。Secret 是底线模板占位符 运行时注入 gitignore 本地凭据文件仓库里永远搜不到明文密码。暴露方式分级集群内部互访用 ClusterIP对外演示用 NodePort生产环境应上 Ingress/LoadBalancer。延伸方向MySQL 上 StatefulSet 主从复制Redis 上 Sentinel/ClusterNginx 对接 Ingress 统一入口后续篇目。下一篇《Prometheus Grafana 监控告警体系搭建全记录》将给这套集群装上眼睛。配套源码https://gitcode.com/cpyaxjq/k8s-ai-ops deploy/yaml/*.yaml、scripts/apply_apps.py、scripts/verify_apps.py