把配置挪到镜像外面,变了什么
一句话总结
ConfigMap 和 Secret 不是“配置仓库”,而是让同一镜像复用于多个环境的注入点;securityContext 和 resources 也不是“选项”,而是约定该容器能在节点上做什么、能使用多少资源的契约。
为什么需要它
把配置烘焙进镜像,就必须为开发、预发布和生产分别制作镜像。这样“在预发布通过的镜像”与“部署到生产的镜像”便不是同一件东西,测试也失去意义。因此要拆成不可变制品(镜像)+ 可变配置(ConfigMap/Secret)。
注入方式分两类。环境变量在进程启动时读取一次,此后绝不会改变。卷挂载以文件形式注入,修改 ConfigMap 后 kubelet 会更新文件内容(但通过 subPath 挂载的项不会更新)。应用能重新读取配置时适合用卷,只在启动时读取一次则使用环境变量更方便。
resources 有两张面孔。requests 是scheduler 看到的值,节点必须有相应余量才能放置 Pod。limits 是 kubelet/runtime 强制执行的值。CPU 会被 throttle,内存超限则 OOMKilled。两者关系决定 QoS class,也决定节点受压时谁先被驱逐。
它如何运作
配置注入的选择如下。
| 方法 | 字段 | 特点 |
|---|---|---|
| 单个键作为环境变量 | env[].valueFrom.configMapKeyRef |
注入时可以改名 |
| 全部作为环境变量 | envFrom[].configMapRef |
键名直接成为变量名 |
| 作为文件 | volumes[].configMap + volumeMounts |
会反映更新,可用 defaultMode 指定权限 |
| 只把一个文件放入现有目录 | volumeMounts[].subPath |
不覆盖目录,不会更新 |
设置 optional: true 后,即使引用对象不存在,Pod 也能启动,适合对象缺失时使用默认值的应用。设为 immutable: true 的 ConfigMap/Secret 无法修改,但 kubelet 无需监视变更,可降低大型集群的 API server 负载。
Secret 并非加密,只是 base64 编码,在 etcd 中近似明文。真正的保护有三项:通过 RBAC 缩小可读主体;为 apiserver 配置 --encryption-provider-config 进行静态加密;只向 Pod 挂载必需内容。
securityContext 分为 Pod 级与容器级。Pod 级(spec.securityContext)的 runAsUser、runAsNonRoot、fsGroup 默认应用于所有容器;容器级(spec.containers[].securityContext)的 capabilities、readOnlyRootFilesystem、allowPrivilegeEscalation 只应用于该容器,并覆盖 Pod 值。fsGroup 只存在于 Pod 级,因为卷是 Pod 级资源。
QoS 是自动决定的只读值。
- 所有容器都满足
requests == limits(CPU 与内存均满足)→ Guaranteed - 至少有一个 requests 或 limits,且不满足上项 → Burstable
- 完全没有设置 → BestEffort(节点受压时最先驱逐)
LimitRange 与 ResourceQuota 是一对。LimitRange 决定单个容器的默认值、最小值和最大值,ResourceQuota 限制整个 namespace 的总量。设置 quota 的 namespace 会直接拒绝未声明 requests/limits 的 Pod,而 LimitRange 填充默认值后即可通过,所以两者通常一起使用。
在实际工作中会遇到的情况
家庭实验室把 NAS 接入 Kubernetes volume 时,为确认 NFS mount 启动了 privileged: true Pod,却得到以下失败。
mount.nfs: Operation not permitted for 10.0.0.109:/volume1/k8s on /mnt/t
mount: permission denied (are you root?)
即使赋予最高权限仍被阻止,原因是缺少 hostNetwork: true。NFS mount 与 rpcbind 通信时,会使用小于 1024 的 reserved port 作为 source port,而该行为在 Pod network namespace 内受限。**privileged 赋予 capability,却无法解决 network namespace 问题。**这是一次代价高昂的教训:securityContext 能解决的问题与 Pod spec 其他字段能解决的问题并不相同。
同一集群的 GPU 则带来了相反方向的教训。Pod 只写一行 resources.limits: {nvidia.com/gpu: 1},scheduler 就选择有 GPU 的节点,runtime 也会注入设备,requests 是 scheduling 语言这一点清晰可见。但随即出现新问题:四台 worker GPU 分别为 24GB(3090)、32GB(5090)和 8GB(4070 Laptop)×2,对 Kubernetes 来说全都是“1 个 GPU”。需要 32GB 的训练真的曾被放到 8GB 笔记本 GPU。最终自行添加 gpu.homelab/tier=xlarge|large|small 等语义标签,再通过 nodeSelector 选择。资源请求只能表达量(quantity),无法表达质(quality),这个限制由标签补足。
后续实验要做什么
在 ckad-config namespace 中通过 literal 和文件创建 ConfigMap,亲手指定 envFrom、configMapKeyRef、卷挂载、defaultMode、subPath、optional、immutable。随后在 ckad-secure namespace 中创建 ServiceAccount、Pod/容器级 securityContext、resources、LimitRange、ResourceQuota,并确认实际 QoS class。