进程不能写根文件系统或切换到零号用户,但可以使用显式允许写入的工作卷。

安全上下文需要运行时证据来验证

Kubernetes 的 securityContext 配置进程身份及部分操作系统约束,可以缩小被攻陷应用允许执行的操作,但本身不会建立完整安全边界。应先确定应用实际需要什么操作,再配置对应字段,并在最终容器中同时测试允许与拒绝的行为。只有一份看起来严格的清单,还不能说明运行中的进程已经按预期受到限制,也不能代替应用本身的权限设计。 把设置放在正确层级 Pod 级配置可以提供用户和组身份、共享卷的组权限行为,以及 seccomp 配置。容器级字段可以覆盖适用的默认值,并设置 capabilities、权限提升和只读根文件系统。需要核对字段真实 API 范围,把熟悉的名称写在错误位置,并不会获得等价约束。相同的 securityContext 名称之下,不代表所有字段都能互换放置。 runAsUser 选择数值用户身份,runAsNonRoot 则要求容器符合非 root 策略。这些设置补充镜像元数据,不能仅凭一个用户名看起来普通,就信任实际权限。卷权限也必须允许所选身份完成必要工作。下面的 emptyDir 通过 fsGroup 提供组访问,使应用能够写入指定工作目录,而不必因此获得对整个根文件系统的写权限。 Linux capabilities 把传统上广泛关联于 root 的权限拆成不同能力。删除 ALL 会移除容器配置中的这些能力。普通容器默认并不继承全部 Linux 能力,而容器内以 root 运行,也不等于已经成功逃逸到宿主机。应审阅真实有效权限,避免用这两种过度简化替代证据。减少权限很有价值,但其效果需要准确描述。 坏掉的样子 allowPrivilegeEscalation false 会为进程设置 no_new_privs,限制通过执行程序获取新权限,例如 set-user-ID 或文件能力引起的身份与权限提升。它不会撤销进程已经拥有的全部权限,也不代替业务授权。应检查实际进程标志和有效能力,不能把每一次被拒绝的操作都归因于这个字段,否则可能误解其他身份或文件权限设置的作用。 readOnlyRootFilesystem 让容器根文件系统只读,其他挂载仍可能可写,包括明确提供的工作卷。这并不是全面禁止执行代码:解释器仍可以读取并运行可写卷中的程序。它也不会阻止网络请求,或撤销对已经挂载数据的读取。只读存储约束的是一类操作,不能直接证明攻击无法继续,更不能把“不能改根目录”扩写成“不能写入任何内容”。 ...

十一月 20, 2027 · 3 分钟 · XB Field Notes
依据目标指标调整工作负载副本数的控制器。

水平自动伸缩按需扩缩容

自动伸缩器根据当前用量与目标值推算出期望副本数,因此选对指标,往往比一开始把副本数定成多少更重要。 指标决定副本数 HPA 并不直接盯着负载看,它按周期采样你配置的指标,把实时读数与你设定的目标值比出比率,再由它推导出期望副本数:超过目标就扩容,不足就缩容,分母是当前 pod 的平均值。所以在它背后,选哪个指标、怎么聚合(按 pod 还是按集群)、以及取多长的窗口,才是真正决定「什么算负载」的东西。算出来的副本还会被 maxReplicas 与最小副本数夹住,并在默认同步周期里逐次校准。想让伸缩可预测,靠的是理解这条指标本身,而不是记住某一个常数。 扩容更激进、缩容更克制 缩容被刻意设计得不对称:控制器绝不会在利用率刚刚下降的那一瞬间动手,因为它要躲开瞬时尖峰,免得副本数像橡皮筋一样来回震荡。它会等一个稳定窗口(downscale-stabilization),守住 minReplicas 这条底线,然后才真正缩。扩容总是更快,因为排队那一刻用户已经感到了慢;缩容放慢,是因为留着多余副本远比让集群来回颠簸便宜。一套务实的参数是:下限留足真实尖峰的余量,目标值取一个能消化负载的比值,并把稳定窗口当成「我容忍多少负载噪声」的加注,而不是凭手感乱调的旋钮。 指标才是薄弱环节 整台控制器只会继承你喂给它的指标,指标怎样,伸缩器就怎样追。指标滞后于真实需求,控制器就总是在追赶慢半拍的数字;指标找一个与真正瓶颈相关性很差的代步量,伸缩器就是在缩错的东西。所以 HPA 最难的不是那几行 YAML,而是选哪个指标、定多少目标。给一条延迟敏感的服务配 CPU 目标、给 I/O 敏感的工作配内存目标,往往会让伸缩器朝反方向走。先搞清真正先饱和的是什么——队列深度、在途请求、连接占用——选择你可以在一个窗口内行动的平均值,并验证「当这个指标上升时,真实延迟确实跟着变坏」。到那时,HPA 才是伸缩你的流量的工具,而不是一台在废坐标上乱猜的机器。 坏掉的样子 指标一旧,伸缩就开始来回晃。管线延迟一两分钟时,控制器拿到的永远是上一波负载:尖峰过去,它才把副本加上去;加上去又被缩容稳定窗口按住,迟迟下不来。面板上不是跟随负载的曲线,而是一上一下的震荡。这时调目标值没有用,应当先修指标时延与采样窗口,再谈目标定多少。 指标直接缺失是更危险的一种。拉不到指标时,控制器会拒绝动作并报出 ScalingActive=False,这是保护而不是缺陷。最典型的故障是把“没有数据”当成“没有负载”:有人看到利用率掉零就手动缩容,结果把正在服务的容量一起删掉。正确姿势是把缺失当成未知,保持上一次决策,同时去查指标管线与事件流,而不是继续动副本数。 上下限会从另一个方向卡死控制循环。maxReplicas 当初为了省钱设得太小,真尖峰到来时期望副本被死死夹在上限,ScalingLimited=True 一直亮着,排队与超时却在上涨;minReplicas 设得太大,则会掩盖目标值估错的问题,因为底座托住了副本,你永远看不到需求下降后控制器敢不敢缩。最后不要让两个控制器拧同一个旋钮:HPA 管个数、VPA 改 request,请求值一动,利用率分母就变,两边互相追打;滚动更新太激进时,新 Pod 尚未接流量,老 Pod 仍在承压,平均值被稀释后误判为可缩容,副本走出锯齿。看到这种抖动,先把控制面分开排查,再调目标。 快速验证 把下面代码保存到临时目录的 hpa_loop.py,运行 python3 hpa_loop.py。它只用标准库模拟一条控制循环:期望副本等于当前副本数乘以当前指标除以目标值再向上取整,再用上下限夹住;指标缺失时保持上次决策;缩容必须等稳定窗口过期,否则先按住不动。脚本断言:热指标扩容、冷却期内冷指标不缩、窗口过期后可缩、超限被上下限夹住。 ...

十一月 4, 2027 · 2 分钟 · XB Field Notes
修改角色标签会把一个就绪 Pod 移出 Service,但不会自动建立网络访问边界。

Kubernetes 标签定义选择关系,不代替授权

标签是 Kubernetes 用于选择对象的键值元数据。Service 选择器、运维查询和工作负载控制器都可能依赖标签,但每个消费者有自己的 API 和处理方式。因此,标签变化可能改变对象所属集合,同时底层进程仍然健康。审阅时应把标签视为真实消费者的输入,而不是仅用于展示的贴纸,否则看似简单的文字修改也可能改变路由。 确认正在使用哪一种选择语言 标签键采用限定名称格式,值也有字符和长度约束。标签值可以为空;不为空时,其允许字符及首尾位置受到限制,最大长度为六十三个字符。任意长段落或不受约束的 JSON 并不适合放在标签值里。描述性信息可以采用 annotation,但仍然要遵守对应格式和消费契约,不能把换字段当成取消所有限制。 等值条件检查某个键是否具有指定值,集合条件还支持成员关系与存在性检查。同一个选择器里的多个条件需要同时满足。有一个容易忽略的细节:track!=canary 也会匹配根本没有 track 键的对象。如果不允许键缺失,应另外要求这个键存在,不能认为“不等于某值”就已经隐含“这个键肯定存在”。 不同资源的选择字段结构并不相同。普通带选择器的 Service 使用等值映射,不能把完整集合表达式随意塞进某个映射值。另一些 API 提供 LabelSelector 结构,而 kubectl 列表查询能够接受选择表达式。把命令行中方便使用的查询转换成清单之前,应核对目标字段的真实结构,避免把同一个概念误当成完全相同的输入格式。 坏掉的样子 Service 选择器会识别本命名空间中匹配的 Pod,端点机制再跟踪地址与就绪状态。标签匹配本身不证明服务已经准备好,也不证明端口配置正确。在普通就绪策略下,路由可用目标还取决于端点条件。检查时应查看相应 EndpointSlice 和应用探测结果,不能在一次标签查询成功后就停止验证。 publishNotReadyAddresses 设置会改变服务发现对就绪状态的处理。在这种配置下,不能仅凭端点被标成就绪,就断言对应容器已经通过应用探测。下面实验采用普通默认行为,没有测试这个覆盖设置,因此结果也不应无条件推广到使用该选项的服务。 给健康 Pod 修改标签,可能把它移出 Service,却并不终止进程。过宽的条件则可能把原本不该提供服务的工作负载加入后端。如果 Pod 由控制器管理,手工改标签还可能影响所属关系与协调行为。应通过负责的控制器更新预期状态,并在改变共享键含义之前核对主要消费者,避免修好一个查询却破坏另一条路由。 选择器不是网络授权。某个 Pod 即使不属于一个 Service,也可能继续通过自身地址或其他 Service 被访问,除非另有控制阻止。NetworkPolicy、应用授权和路由分别解决不同部分,不能把选择器描述成能够拒绝所有未选中对象直连流量的访问名单。对象是否被发现,与请求最终是否允许,必须分别成立。 ...

九月 9, 2027 · 3 分钟 · XB Field Notes
两个命名空间共享节点容量,但对象名称、API 权限与配额判断分别受到明确规则约束。

命名空间划分对象范围,其他边界需要政策

命名空间为 Kubernetes 集群中的许多对象限定范围。两个团队都可以创建名为 settings 的 ConfigMap,因为包含命名空间后的完整身份不同。这种组织边界很有价值,但不会自动提供网络隔离、独立内核、专属节点容量或完整租户隔离。工作负载实际需要哪一种保障,就应确认额外由什么政策和执行机制落实,而不是把它们都归因于命名空间。 名称范围取决于资源类型 Pod、Service、ConfigMap 和许多工作负载控制器属于命名空间资源。Node、命名空间本身以及其他集群级资源,则不会因为客户端指定了某个命名空间,就被划分成另一份独立副本。应查看具体资源的 API 范围,不能假定所有对象都住在某个房间里面。命名空间也不是由独立子集群构成的嵌套层级。 在 Pod 内部,服务短名称通常按所在命名空间解析。带限定的名称可以指向别处,例如默认集群域下的 api.other.svc.cluster.local。这种约定负责定位目的地址,并不决定允许还是拒绝连接。集群域也可以另行配置,因此示例中的后缀不是所有安装都必须采用的常量。诊断 DNS 时需要结合实际域和工作负载所在位置。 自动化中的命名空间选择应保持明确。对象名称即使正确,如果当前活动命名空间错误,仍然会访问另一个资源。尤其是修改操作,应有意指定 kubeconfig、上下文和命名空间,而不是依赖操作者终端之前留下的状态。对象范围让身份更清楚,但具有广泛权限的管理员依然能够跨范围修改内容,不能因此省略权限审查。 坏掉的样子 没有适用网络政策的命名空间,仍然按照集群网络本身的行为保持连通。仅创建命名空间,或在 DNS 中加入它的名称,并不会拒绝其他 Pod。网络隔离需要正确的政策,以及真正执行这些政策的网络实现。应用认证还是另一层:域名能解析,只说明找到了地址,并不能证明某个调用者应该获准读取返回内容。 RBAC 则单独控制 API 操作。一个 RoleBinding 可以让账号读取本命名空间的 ConfigMap,而不给它读取其他范围的能力。但权限是叠加的,另一个广泛绑定仍可能授予更多访问。应以目标身份实际验证允许和拒绝的请求,不能认为添加一份较小角色,就自动取消先前更大的授权。网络流量政策也不会代替这一层 API 权限。 ResourceQuota 主要在准入阶段约束对象数量,以及汇总的资源请求或限制等指标。它本身不会节流正在运行的进程,也不会预留物理节点容量。LimitRange 可以为新接纳的工作负载提供默认值和边界,运行时资源限制则由其他机制执行。这些政策都需要明确配置;只有一个命名空间,并不能让会争抢共享资源的邻居自动变得无害。 快速验证 准备使用默认 cluster.local 域的临时 Kubernetes 集群,确保 kubectl 管理员能够执行测试 impersonation,节点已有 python:3.11-slim-bookworm 镜像。把代码保存为 namespaces.py,执行 python3 namespaces.py KUBECONFIG_PATH CONTEXT,明确传入测试配置。实测集群是 Kubernetes 1.34.11,采用 kind 网络。脚本创建两个唯一命名空间,并在结束后删除两者。 ...

八月 25, 2027 · 4 分钟 · XB Field Notes
一个稳定的名字,站在一组会变动的 Pod 前面。

Kubernetes Service 为 Pod 提供稳定入口

既然 Pod 会重建,就很难记住它的地址。Service 在这组 Pod 前面固定一个名字,让调用方不因 Pod 更换而受影响。 一个地址,一组 Pod 多副本带来了多个 Pod,而每个 Pod 都有自己的 IP,还会随时被重建。Service 正是它们前面那个稳定的家:你声明一个选择器,平台就分给它一个固定的集群 IP 和一段可被 DNS 解析的名字。集群 DNS 把名字解析到 Service 上,因此组件(Ingress、负载均衡、别的 Pod)只依赖这个名字,而从不关心具体某个 Pod 的地址。抽象的全部意义就在于:名字是持久的,背后站着的每一样东西都可以即丢即换。 选择规则决定由谁响应 Pod 靠「匹配」加入 Service,而不是靠被人点名登记:标签打中了选择器,它就进入;不再匹配或不再就绪,就被移出。平台从选择器推出后端这份视图,当一个 Pod 通过探针与就绪,就把它加进去,缩容或下线时就拿掉。所以后端列表是一份实时推导的视图,而不是从头到尾手打的名单。也正因为后端由选择器与就绪共同决定,一个没就绪的 Pod 会自己掉出去,流量绝不会被送到不该服务的 Pod 上。 流量总给当前匹配的 这层间接,正是让端点变化落不到调用方身上的原因。客户端只握着那个名字;它背后的集合被扩、被替、被滚动更新,都不需要改客户端设一个字。滚动更新一个接一个地替换掉又续上 Pod,Service 始终把流量导向当前匹配且就绪的那批,于是调用方察觉不到任何断层。你可以依赖这件事:只要选择器精准、Pod 与它匹配,Service 在每个时刻都把流量送进正确的那批。把选择器钉紧、探针管严,这个稳定的家便能在里面的 Pod 全是新面孔时依旧稳如磐石。 ...

八月 23, 2027 · 2 分钟 · XB Field Notes
先执行启动检查,成功后才开始就绪与存活检查。

启动探针成功后,其他探针才开始执行

Startup 探针为容器提供单独的初始化契约。在已经配置的启动探针成功之前,Kubernetes 不会执行这个容器的 liveness 或 readiness 探针。这比“宽限期内忽略一些存活失败”更具体:另外两种检查根本还没有开始。启动检查成功之后,后续健康和就绪判断才按各自规则接管,不能把三个名称理解成同一个接口的不同标签。 为每个阶段定义可观察条件 应用进程必须先运行,启动探针才可能成功。因此,容器已经 Running 时,container status 中的 started 仍可能为 false,所在 Pod 也可能尚未 Ready。运行状态描述进程正在执行,启动成功则描述配置的检查条件已经满足。提前开放的 HTTP 监听端可以报告进度,但不应该因此自动声称全部准备已经完成。 启动条件应代表必要的本地准备,例如加载索引或验证初始状态。只有打开端口确实就是所需前置条件时,单纯检查端口才足够。如果监听器先出现,昂贵的初始化稍后才结束,过早成功的检查就会提前解除存活保护,再次造成原本想避免的重启循环。接口名称不会替应用定义初始化完成的真实含义。 启动检查一旦成功,kubelet 就停止当前容器实例的 startup 探针,并按独立配置运行存活与就绪检查。启动成功不是永久健康证明:应用稍后仍可能变成未就绪,或者需要重启。新的容器实例还要重新完成自己的启动检查,不能继承上一实例已经成功的结论。把实例身份和状态变化一起记录,才能解释重复启动的原因。 坏掉的样子 启动探针如果一直不成功,最终会达到失败阈值。Kubelet 随后终止容器,并根据对应重启策略处理。因此,启动保护不是允许初始化无限等待。周期、超时和失败阈值应依据真实初始化耗时设置,同时考虑环境中可能发生的资源争用与可以接受的恢复时间,避免只按开发机器的一次快速启动选择参数。 failureThreshold 与 periodSeconds 的乘积可用于规划启动预算,但不能把它写成所有实际重启都严格遵守的秒表。初始延迟、探针执行与调度、终止宽限和重启退避也影响完整时间线。排查慢发布时,应记录真实尝试和状态变化,而不是仅根据两个配置数字推断容器应该已经重启,或断定平台行为错误。 除非通过 startup 明确建立依赖,否则不应认为 liveness 会等 readiness 成功才开始。就绪与存活原本回答两个持续运行阶段的问题。反过来,启动成功也不会强迫就绪成功:后续 readiness 可能因为另一个条件拒绝流量资格,而进程仍然正常存活。把这些结果分开,才能选择正确恢复动作,避免用反复重启处理暂时不可服务。 ...

八月 22, 2027 · 3 分钟 · XB Field Notes
被拒绝直接读取的主体,仍能创建 Pod 并接收一个选定的合成示例键。

Kubernetes Secret 需要明确的访问边界

Kubernetes Secret 是用于保存工作负载所需机密值的 API 对象,提供专门的类型、消费路径和访问控制目标。但它不会让所有其他参与者都无法读取内容。设计边界时,需要同时考虑谁能取回对象、谁能创建消费者、谁能查看消费者输出,以及谁能管理运行它们的节点。仅区分配置对象与秘密对象,还不足以完成权限设计。 编码与保护处于不同层次 在 JSON API 表示中,Secret 的 data 使用 base64;stringData 则便于在写入时提交字符串。Base64 无需密钥就能还原,并不是加密。这种表示也不能证明某个具体集群怎样保护底层存储。Kubernetes 文档说明,若未配置相应加密,秘密数据默认不在存储层加密。应核对实际 API 服务器和存储设置,不能看到编码文字就推断保密措施已经生效。 存储加密同样不会阻止获得授权的 API 读取者拿到数据。RBAC 中的 get、list 和 watch 权限都可能暴露 Secret 内容,广泛的列表权限并不只是允许查看对象名称。备份和诊断输出也需要保护。正常应用最终仍要获得可用值,因此,一旦有权消费数据的程序被控制,攻击者就已经进入这份数据的信任边界,不能靠编码再次隔离它。 Secret 适合小规模值,单对象有一 MiB 限制。应只挂载需要的键,并只交付给真正需要它的容器。文件模式可以缩小工作负载内部的读取范围,却不能替代 API 授权或对创建工作负载权限的控制。环境变量注入也有暴露范围和生命周期方面的取舍,应根据应用需求选择,不能默认它一定比文件更安全。 坏掉的样子 禁止直接 get Secret,不一定禁止间接取得内容。主体如果可以在命名空间创建 Pod,通常就能在自己提交的规范中指定一个 Secret 卷,由 kubelet 把数据交给该工作负载。创建相应控制器的权限也可能产生类似效果。因此,除了直接读取数据的规则,还必须一起审查创建工作负载的权限及适用的准入限制,否则边界会漏掉实际消费路径。 ...

八月 21, 2027 · 3 分钟 · XB Field Notes
三次替换的 Pod 使用同一镜像,依次选择蓝色、绿色和保留的蓝色配置。

ConfigMap 把配置与镜像分开

ConfigMap 把非敏感配置放在镜像之外,让环境相关设置发生变化时,不必重新构建应用产物。真正有用的边界需要明确:镜像提供代码,工作负载定义则指定这些代码应该读取哪份配置。因此,一次发布不仅是一个镜像标签,配置引用同样属于正在交付的行为。只记录镜像版本,可能无法解释两个环境为什么表现不同。 给配置一个可审阅的身份 ConfigMap 的 data 值是字符串,也可以通过 binaryData 表达二进制内容及其 API 编码形式。它适合小规模配置,数据大小有一 MiB 限制,不是通用文件仓库,更不是保护秘密的机制。API 能检查对象结构,却不知道某个应用参数在业务上是否合理。因此,结构合法和业务正确仍需分别验证,不能以创建成功代替配置审查。 Pod 可以通过环境变量引用或挂载文件消费配置。普通 Pod 引用要求 ConfigMap 位于这个 Pod 所在的命名空间。名称、必需键和应用解释方式都应明确。Deployment 对象被 API 接受,并不证明它引用的每个键都存在,也不证明新容器能够带着这些值成功启动。发布检查必须继续观察真实工作负载的结果。 单位、允许值与默认值应属于应用配置规范。某个字符串看起来像布尔值或数字,并不说明接收方会采用什么转换规则。在不同环境之间推广配置之前,需要结合真实消费者检查这些规则,避免同一份文本被解释成不同业务含义。 版本化的不可变 ConfigMap 更容易表达发布身份。settings-v2 可以与 settings-v1 同时保留,而 immutable 会禁止修改当前对象的数据。但这项保护不阻止有权限的操作者删除对象,再用同名重新创建另一份内容。对象生命周期权限和发布记录,应与希望实现的不可变承诺一致,不能把一个字段理解成所有可能路径都被永久封闭。 坏掉的样子 修改配置与替换进程是两种不同动作。已经注入运行中容器的环境值,不会因为 ConfigMap 更新而被改写。普通投射文件可能最终更新,但应用仍要重新读取;subPath 挂载和已经打开的文件又有不同生命周期。因此,API 中出现了新值,并不能直接宣布应用已经完成配置切换,还需要查看消费者实际读到了什么。 把 Deployment 的 Pod 模板改为引用新的 ConfigMap,是明确的滚动替换触发条件。仅修改 ConfigMap 本身,不会自动改写这个模板。同样,校验和 annotation 只有在某个工具真正把它更新到 Pod 模板中时才起作用。Kubernetes 不会自行发现任意外部配置变化,并替应用生成期望的校验和;这部分自动化需要有真实实现。 ...

八月 20, 2027 · 3 分钟 · XB Field Notes