知序
返回
保存草稿
发布
H1
H2
H3
1
2
3
Excalidraw 图
AI 写作
AI 优化
目录
输入内容后显示目录
Markdown
# 03 - 配置与数据:ConfigMap / Secret / Volume > 本篇目标:把"配置"和"数据"从镜像里拆出去。学完你能做到 —— 同一个镜像,靠不同配置跑在开发/测试/生产;容器重启后数据还在。 > 前置:01、02 篇(服务已能被访问)。 --- ## 1. 原则:配置与镜像分离  *图:配置与镜像分离:配置由 ConfigMap / Secret 运行时注入,镜像保持环境无关* ```text ❌ 错误做法:把数据库密码打进镜像 ┌──────────────────────────┐ │ 镜像 blog-system:1.0.0 │ │ application.yml │ │ password=prod123 │ ← 镜像变成了"环境专属" └──────────────────────────┘ 后果:改一次密码要重新构建、重新推送、重新发布; 镜像推到仓库就等于把密码推到了仓库。 ✅ 正确做法:镜像保持干净,配置运行时注入 ┌──────────────────────────┐ │ 镜像 blog-system:1.0.0 │ ← 一份镜像,处处可用 └────────────┬─────────────┘ │ 运行时注入 v ┌──────────────────────┐ │ ConfigMap(普通配置) │ │ Secret(敏感信息) │ └──────────────────────┘ ``` --- ## 2. ConfigMap:普通配置 ```yaml # configmap.yaml apiVersion: v1 kind: ConfigMap metadata: name: blog-config data: SPRING_PROFILES_ACTIVE: "prod" application.properties: | logging.level.root=INFO blog.site.name=知序 ``` 注意 `data` 下面有两种写法: - **键值对**:`SPRING_PROFILES_ACTIVE: "prod"` - **整个文件**:`application.properties: |` 后面跟多行内容 --- ## 3. Secret:敏感信息,但它不等于加密 ```yaml # secret.yaml apiVersion: v1 kind: Secret metadata: name: blog-secret type: Opaque stringData: # 用 stringData 可以直接写明文,K8s 会帮你 base64 SPRING_DATASOURCE_USERNAME: blog SPRING_DATASOURCE_PASSWORD: "请换成强密码" ``` ```bash # 验证一下 base64 有多"不加密" kubectl get secret blog-secret -o jsonpath='{.data.SPRING_DATASOURCE_PASSWORD}' | base64 -d # 直接把密码打出来了 ``` > ⚠️ **Secret 不等于加密**。它默认只是 base64 编码,存储在 etcd 里,任何能读到该 Secret 的人都能解出来。 > > 真正的做法: > 1. 开启 **etcd 静态加密**(`EncryptionConfiguration`) > 2. 用 RBAC 收紧密钥的读权限(只有需要它的 ServiceAccount 能读) > 3. 更高要求用外部密钥管理(Vault / 云厂商 KMS) > > **最低限度:别把生产密码当成"K8s 已经帮我加密了"就放心提交进 Git。** --- ## 4. 怎么注入到容器 ```yaml spec: containers: - name: app image: registry.example.com/blog-system:1.0.0 envFrom: - configMapRef: name: blog-config # 整个 ConfigMap 变环境变量 - secretRef: name: blog-secret volumeMounts: - name: config-vol mountPath: /app/config # 也可以挂成文件 readOnly: true volumes: - name: config-vol configMap: name: blog-config ``` **两种方式的取舍:** | | 环境变量 | 挂载成文件 | |---|---|---| | 改动后生效 | 需要重启 Pod | 挂载的 ConfigMap 更新会自动同步(有延迟,通常几十秒) | | 适合 | 开关、profile、短配置 | 完整配置文件(application.yml、nginx.conf) | | 注意 | 配置多了会污染环境变量空间;**环境变量在容器启动后就固定了** | 需要应用支持"重新加载配置",否则还是要重启 | | 排障 | `kubectl exec ... -- env` 直接看 | 看挂载目录里的文件内容 | --- ## 5. 数据怎么持久化:Volume 入门 容器里的文件系统是**临时的** —— 容器重启,写进去的东西就没了。要持久化,得挂 Volume。  *图:存储三层次:emptyDir / hostPath / PV + PVC + StorageClass 的取舍* ```text 三个层次,按需要选: ┌────────────────────────────────────────────────────────────────┐ │ 1. emptyDir:Pod 生命周期内的临时目录 │ │ 用途:容器间共享文件、缓存 │ │ ⚠️ Pod 一删就没了 │ ├────────────────────────────────────────────────────────────────┤ │ 2. hostPath:挂节点上的目录 │ │ 用途:单机测试、读节点上的日志 │ │ ⚠️ Pod 换节点就找不到数据,生产基本别用 │ ├────────────────────────────────────────────────────────────────┤ │ 3. PV + PVC(持久卷):真正生产用的方式 │ │ │ │ 存储管理员/云厂商 提供 PV(实际存储:云硬盘 / NFS / Ceph) │ │ ▲ │ │ │ 绑定(按容量 + accessModes 匹配) │ │ │ │ │ 应用声明 PVC(我要 20Gi 可读写) │ │ ▲ │ │ │ 引用 │ │ Pod 的 volumeMounts │ │ │ │ StorageClass = 动态供给:PVC 一提交,云盘自动创建,不用手工建 PV │ └────────────────────────────────────────────────────────────────┘ ``` ```yaml # pvc.yaml —— 云上通常配好 StorageClass,PVC 一提交磁盘就自动创建 apiVersion: v1 kind: PersistentVolumeClaim metadata: name: mysql-data spec: accessModes: - ReadWriteOnce # 单节点读写(云硬盘就是这种) resources: requests: storage: 20Gi storageClassName: standard ``` **accessModes 要记住的**: | 模式 | 含义 | 常见支持 | |---|---|---| | `ReadWriteOnce` (RWO) | 只能被**一个节点**读写 | 云硬盘、大多数块存储 | | `ReadOnlyMany` (ROX) | 多个节点只读 | NFS 类 | | `ReadWriteMany` (RWX) | 多个节点同时读写 | NFS / CephFS / 文件存储 | > **坑点**:云硬盘是 RWO,意味着 **Deployment 的多副本无法共享同一个 PVC**。如果你给 3 副本的应用挂了一个云硬盘 PVC,另外 2 个副本会一直卡在 `ContainerCreating`(卷已被占用)。需要共享存储就得用 RWX 文件存储。 --- ## 6. 有状态服务怎么办 到这里必须说清楚一件事:**MySQL、Redis 这类有状态服务,在 K8s 上是另一个话题。**  *图:无状态与有状态对比:实例是否等价,决定用 Deployment 还是 StatefulSet* ```text 无状态应用(本文示例) 有状态应用(MySQL / Redis) ──────────────────── ───────────────────────── 每个副本完全一样 每个副本有自己的数据、身份 副本叫 app-xxxxx(随机后缀) 副本叫 mysql-0 / mysql-1(有序) 可以随便杀、随便加 不能随便杀,主从角色有区别 共享同一个 Service 即可 每个 Pod 需要独立的网络标识 → 用 StatefulSet + Headless Service ``` **入门阶段的务实做法**:把数据库**放在集群外**(就像 04 篇的实战那样),让应用彻底无状态化。等你把无状态玩熟了,再去碰 StatefulSet 和 Operator。 --- ## 7. 本篇小结 ```text 配置 → ConfigMap / Secret(运行时注入,镜像保持干净) └─ Secret 只是 base64,别当加密用 数据 → emptyDir(临时)→ hostPath(别上生产)→ PV/PVC(生产用) └─ 云硬盘是 RWO,多副本别共用 有状态服务 → 入门阶段先放集群外 ``` 下一篇把所有东西串起来:拿一套真实的 Spring Boot + MySQL 应用,从镜像一路做到外网可访问。
预览
发布文章
添加摘要让读者更快了解文章内容
取消
确认发布
AI 写作助手
输入一个主题,AI 将为你生成一篇 Markdown 博客文章
AI 正在写作中...
取消
开始生成
AI 优化助手
描述你的修改需求,AI 将在原文基础上进行补充或微调
AI 正在优化中...
取消
开始优化
画布
从已有图里选
✕
图名
保存后生成 /images/excalidraw/<图名>.svg(只能用字母、数字、短横线、下划线、点)
取消
保存并插入