LabHub
学习 学习路径 课程

CKAD — Kubernetes 应用开发者

把配置挪到镜像外面,变了什么

在 LabHub 中继续学习

一句话总结

ConfigMap 和 Secret 不是“配置仓库”,而是让同一镜像复用于多个环境的注入点;securityContext 和 resources 也不是“选项”,而是约定该容器能在节点上做什么、能使用多少资源的契约

概念图: 让同一镜像复用于多个环境的注入点 · 约定该容器能在节点上做什么、能使用多少资源的契约 · 不可变制品(镜像)+ 可变配置(ConfigMap/Secret) · 环境变量

为什么需要它

把配置烘焙进镜像,就必须为开发、预发布和生产分别制作镜像。这样“在预发布通过的镜像”与“部署到生产的镜像”便不是同一件东西,测试也失去意义。因此要拆成不可变制品(镜像)+ 可变配置(ConfigMap/Secret)

注入方式分两类。环境变量在进程启动时读取一次,此后绝不会改变。卷挂载以文件形式注入,修改 ConfigMap 后 kubelet 会更新文件内容(但通过 subPath 挂载的项不会更新)。应用能重新读取配置时适合用卷,只在启动时读取一次则使用环境变量更方便。

resources 有两张面孔。requestsscheduler 看到的值,节点必须有相应余量才能放置 Pod。limitskubelet/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)的 runAsUserrunAsNonRootfsGroup 默认应用于所有容器;容器级(spec.containers[].securityContext)的 capabilitiesreadOnlyRootFilesystemallowPrivilegeEscalation 只应用于该容器,并覆盖 Pod 值。fsGroup 只存在于 Pod 级,因为卷是 Pod 级资源。

QoS 是自动决定的只读值。

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,亲手指定 envFromconfigMapKeyRef、卷挂载、defaultModesubPathoptionalimmutable。随后在 ckad-secure namespace 中创建 ServiceAccount、Pod/容器级 securityContext、resources、LimitRange、ResourceQuota,并确认实际 QoS class。