一、这个场景是怎么来的

先别急着看代码,咱们先把一个非常真实的处境还原出来。

你手里有一套运行了好几年的 Kubernetes 集群,节点上跑着很多业务 Pod。某天安全通告出来,说内核存在一个致命漏洞,攻击者可能通过某个系统调用直接拿到宿主机权限。正常的修复流程很简单:打补丁,重启节点,一切恢复平静。

可问题就出在重启这两个字上。集群里跑的是线上交易系统、实时推荐服务、支付回调,任何一个节点重启,上面的 Pod 全得漂移,流量重新调度,连接全断,就算有滚动更新机制,业务方也大概率会跳出来喊停。更麻烦的是,有些无状态应用还好说,真正怕的是那些持有本地缓存、依赖宿主机 Socket 连接、或者有状态存储的应用,一重启就是事故。

这时候你需要一种更务实的手段:在不重启节点内核的前提下,尽量把漏洞的杀伤力压到最低。要么在操作系统层面做热修复,要么在 Kubernetes 这一层收紧 Pod 的权限和能力,把攻击面尽可能封死。等业务低峰期再申请重启窗口,打上正式补丁收尾。

这篇文章就是围绕这个思路展开的。你会看到纯粹靠 Kubernetes 自身能力就能完成的临时加固方案,也会看到一些轻量级内核热修复思路,整个过程不依赖特定云厂商,也不要求你有汇编基础,全部用现有工具就能落地。

二、漏洞来了,可节点不能重启

2.1 到底想挡住什么

我们说的内核漏洞,往简单了讲,就是攻击者拿一个普通权限的进程,通过某些特殊方式,摸到了内核不该被访问的数据,或者执行了不该被执行的代码。典型例子像脏管道、堆溢出、符号链接绕过之类的漏洞,最后效果基本都是提权。

既然无法升级内核,那思路就转化为三件事:第一,让普通容器里的进程没那么容易触发漏洞路径;第二,就算触发了,降低它拿到宿主机权限的概率;第三,就算拿到了权限,也拦着它横向扩散。

Kubernetes 里恰好有对应的工具链,PodSecurityContext、SecurityContext、Pod Security Standards、NetworkPolicy、Seccomp,全都能用来做这个事。

2.2 热修复不等于升级内核

很多朋友一听热修复就想到内核热补丁,其实那是更偏底层的东西。这里先介绍一种更简单、风险更可控的操作级热修复思路,后面再讲怎么用 Kubernetes 的机制把风险临时腰斩。

下面所有示例都会围绕同一个技术栈展开:Kubernetes + Shell 命令行,版本号以 Kubernetes 1.28 举例。

三、用内核热补丁撑过临时窗口

3.1 常见的操作级热修复

严格来说,真正给内核打 live patch 需要用到 kpatch、kGraft、livepatch 这类高级方案,它们能把补丁加载进运行中的内核,不重启节点。这类方案不是所有发行版都能用,而且需要注册商业订阅,不适合拿来随笔就写。

更适合大多数团队的是另一条路线:如果已知某个漏洞是某个内核参数、某个模块、某个配置开关导致的,可以直接在运行中的节点上调整对应的 sysctl 参数,或者卸载有风险的模块,这其实也算局部热修复的一种。比如某些高危协议模块,只要确认业务没用,卸载掉就能堵住攻击面。

先看看当前节点的内核版本和加载了哪些有嫌疑的模块。


# 技术栈:Kubernetes + Shell

# 查看当前节点的内核版本,确认是否命中漏洞公告中提到的版本范围
uname -r

# 查看某个具体内核模块是否被加载,例如有历史漏洞的模块
# 这里以老旧的 bridge netfilter 模块举例说明思路
lsmod | grep br_netfilter

# 查看当前节点的 sysctl 配置,定位是否有可以被利用的参数处于宽松状态
sysctl -a --pattern 'kernel.*unprivileged' 

# 如果业务不可能用到该模块,可以临时将其禁用
# 注意:生产环境需要确认模块没有在跑任何业务
sudo modprobe -r 模块名

# 写入配置文件让节点重启后依然保持禁用状态
echo "blacklist 模块名" | sudo tee /etc/modprobe.d/临时封禁.conf

这种操作适合快速止血,比如某个漏洞依赖特定模块被加载,卸载掉就等于断了攻击路径。可问题在于,并不是所有漏洞都能靠卸载模块解决,很多时候漏洞就在核心协议栈里,总不能把网络整个停掉。

这时候,Kubernetes 层的加固就必须上场。

3.2 通过 sysctl 临时收缩风险面

某些内核漏洞可以通过把某些全局开关关掉来缓解,比如限制非特权用户访问内核日志、关闭特定协议转发、限制核心转储等。


# 技术栈:Kubernetes + Shell

# 查看当前是否允许非特权用户通过 dmesg 读取内核日志
# 很多提权漏洞需要先读取内核日志获取内核地址,把这个关掉能提高攻击门槛
sysctl kernel.dmesg_restrict

# 临时设置为 1,禁止普通进程读取内核日志
sudo sysctl -w kernel.dmesg_restrict=1

# 查看是否允许非特权用户使用 perf 事件,某些漏洞会借助 perf 做侧信道攻击
sysctl kernel.perf_event_paranoid

# 收紧 perf 权限,只允许内核态使用
sudo sysctl -w kernel.perf_event_paranoid=2

# 限制核心转储,防止内存中的敏感数据被写入到磁盘文件
sudo sysctl -w fs.suid_dumpable=0

# 将以上设置持久化写入配置文件
cat <<EOF | sudo tee /etc/sysctl.d/99-security-hardening.conf
kernel.dmesg_restrict=1
kernel.perf_event_paranoid=2
fs.suid_dumpable=0
EOF

# 重新加载配置,确保不会因为写法错误导致节点下次启动异常
sudo sysctl --system

这段操作的核心价值在于,它不重启进程、不重启节点,只是让内核在运行过程中修改部分行为,攻击者想利用漏洞之前,得先过两道坎。

但这类操作看起来像变魔法,实际操作之前一定要搞清楚,你的业务进程里有没有依赖这些日志接口、性能分析工具的程序,如果有,关掉之后监控和排障就容易变得被动。所以这类操作适合应急,但不适合常驻。

四、Pod 安全策略的临时加固

4.1 从容器权限开始做减法

如果内核漏洞无法立刻堵上,那能不能让容器里的进程根本没有权限去触碰那些危险系统调用?完全可以。Kubernetes 的 SecurityContext 就是干这个用的。

先从一个最简单的方向下手:让 Pod 以非 root 用户运行,并且声明容器的根文件系统只读。这样就能拦掉很多依赖 root 身份、依赖写宿主机路径才能触发的漏洞路径。

下面是一份部署清单。注意,我们说的是用 Deployment 来管理应用,用 Nginx 作为示例镜像演示。


# 技术栈:Kubernetes + YAML 部署示例

# 该示例演示如何对一个普通业务 Pod 进行安全加固
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-secure-example
  namespace: default
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx-secure
  template:
    metadata:
      labels:
        app: nginx-secure
    spec:
      # 容器级别的加固全部写在 securityContext 里,这是整个 Pod 防护最关键的位置
      containers:
      - name: nginx
        image: nginx:1.25
        securityContext:
          # 允许容器以非 root 身份运行
          # Nginx 官方镜像支持通过环境变量切换运行用户
          runAsNonRoot: true
          # 指定运行时用户 ID,使用 101 这个普通用户,避免直接使用 0 号用户
          runAsUser: 101
          # 划分一个文件组权限
          runAsGroup: 101
          # 防止容器内的进程获得额外权限
          allowPrivilegeEscalation: false
          # 禁止容器以特权模式运行
          privileged: false
          # 使用只读根文件系统,容器无法往自己的根目录写文件
          # 临时文件需要通过挂载卷来解决
          readOnlyRootFilesystem: true
          # 删除容器默认自带的所有 Linux 能力
          # 这样容器内的进程几乎没有权限执行敏感系统调用
          capabilities:
            drop:
            - ALL
        # 因为根文件系统只读,nginx 需要把 pid 文件和缓存写到临时目录
        # 这里挂载一个内存 tmpfs 卷给容器使用
        volumeMounts:
        - name: tmp-storage
          mountPath: /tmp
        ports:
        - containerPort: 80
# 声明卷为 emptyDir 类型,适合存放临时文件
      volumes:
      - name: tmp-storage
        emptyDir: {}

这里需要解释几个容易被忽略的细节。readOnlyRootFilesystem 打开之后,很多中间件会报错,因为它们在运行时需要写 pid 文件、缓存文件、socket 文件,解决办法就是准备一个临时空目录挂载进去。capabilities 里直接 drop 掉 ALL,相当于把容器的所有特殊能力都收走了,普通业务一般用不到这些东西,又安全又不影响运行。

4.2 配合使用 Pod 安全准入控制

Kubernetes 从 1.25 开始,PodSecurityPolicy 被移除了,替代方案是 Pod Security Standards。虽然很多人提到旧版 PSP 还是很亲切,但新项目不建议再依赖它。

现在更推荐直接用命名空间级别的 Pod Security 标签来管控。


# 技术栈:Kubernetes + Shell

# 给命名空间打上强制隔离标签
# 这样所有部署到该命名空间的 Pod,如果不满足安全要求,直接无法创建

kubectl create namespace secure-business

# baseline 是基础安全级别,要求关闭特权容器、限制 hostNetwork 等
kubectl label ns secure-business pod-security.kubernetes.io/enforce=baseline

# warn 会在 Pod 创建时给出警告但不拦截,适合先观察
kubectl label ns secure-business pod-security.kubernetes.io/warn=restricted

# audit 会记录审计日志,方便后续排查
kubectl label ns secure-business pod-security.kubernetes.io/audit=restricted

# 查看命名空间的标签确认生效
kubectl get ns secure-business --show-labels

Restricted 是最高级别,对 Pod 的要求很严格。如果你的业务 Pod 能在这个级别下正常跑,说明它本来就是一个相对干净的应用。反过来说,如果之前从没关注过这些配置,用这个策略再多稳定跑几天,很多老毛病都会浮出水面。

4.3 用 Seccomp 限制系统调用

内核漏洞大多依靠某些非常规的系统调用触发。Seccomp 可以让容器内的进程只能使用白名单范围内的系统调用,其它的一律返回错误。

Kubernetes 支持在 Pod 级别配置 seccompProfile。


# 技术栈:Kubernetes + YAML 部署示例

# 这是基于前面 Nginx 例子的升级版本,增加 seccomp 配置
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-seccomp-example
  namespace: secure-business
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx-seccomp
  template:
    metadata:
      labels:
        app: nginx-seccomp
    spec:
      containers:
      - name: nginx
        image: nginx:1.25
        securityContext:
          runAsNonRoot: true
          runAsUser: 101
          runAsGroup: 101
          allowPrivilegeEscalation: false
          privileged: false
          readOnlyRootFilesystem: true
          capabilities:
            drop:
            - ALL
          # 这里启用 seccomp 限制,使用 RuntimeDefault 配置
          # RuntimeDefault 是容器运行时自带的默认系统调用过滤规则
          seccompProfile:
            type: RuntimeDefault
        volumeMounts:
        - name: tmp-storage
          mountPath: /tmp
        ports:
        - containerPort: 80
      volumes:
      - name: tmp-storage
        emptyDir: {}

RuntimeDefault 是绝大多数容器运行时内置的一套安全过滤配置,它不会阻断正常的 Nginx 业务,但可以拦截掉很多有明显风险的系统调用。如果安全团队要求更高的隔离级别,可以自定义 seccomp 配置文件,再通过 localhost 指定路径挂载到节点上。


// 技术栈:Kubernetes + JSON 配置文件示例

// 这是一个自定义 seccomp 配置的片段,只允许必要的系统调用
// 实际使用时需要放到每个节点的 /var/lib/kubelet/seccomp/ 目录下
{
  "defaultAction": "SCMP_ACT_ERRNO",
  "defaultErrnoRet": 1,
  "syscalls": [
    {
      "names": [
        "read",
        "write",
        "exit",
        "exit_group",
        "futex",
        "nanosleep",
        "openat",
        "close",
        "mmap",
        "mprotect",
        "munmap",
        "brk",
        "execve",
        "getpid",
        "getuid"
      ],
      "action": "SCMP_ACT_ALLOW"
    }
  ]
}

这段 JSON 的思路是默认禁止所有系统调用,然后一点点放行业务必需项。好处是极其安全,坏处是应用稍微复杂一点就会出现诡异报错,需要不断补充白名单。临时加固阶段,建议先用 RuntimeDefault 过渡,后续再调优。

4.4 网络层面再套一层护盾

内核漏洞被利用后,攻击者掉下来,第一件事通常就是拉起一个反向 Shell,再尝试访问集群内部其它服务。这时候如果能限制 Pod 的网络访问范围,就能把横向扩散的路径切断。

NetworkPolicy 是 Kubernetes 里专门做这个东西的。注意,要使用它,得确认集群里的网络插件实现了这个 API,例如 Calico、Cilium 或者 Weave Net 都支持。


# 技术栈:Kubernetes + YAML 部署示例

# 限制 nginx-secure 这个应用只允许被指定的前端 Pod 访问
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: restrict-nginx-ingress
  namespace: secure-business
spec:
  # 关联到目标 Pod
  podSelector:
    matchLabels:
      app: nginx-secure
  # 策略类型明确只处理入站流量
  policyTypes:
  - Ingress
  ingress:
  - from:
    # 仅放行带有 app=frontend 标签的 Pod
    - podSelector:
        matchLabels:
          app: frontend
    ports:
    # 仅放行 80 端口
    - protocol: TCP
      port: 80

再来一发狠的,直接把某个命名空间里所有 Pod 的默认出站权限全部掐断,业务需要的通路再单独放行。


# 技术栈:Kubernetes + YAML 部署示例

# 缺省拒绝全部出站流量
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-egress
  namespace: secure-business
spec:
  # 这个选择器会匹配命名空间下全部 Pod
  podSelector: {}
  policyTypes:
  - Egress
  egress:
  # 列表为空就表示没有任何放行规则,全部出站被阻断

这种策略一旦生效,业务马上会断一部分,所以搭配 DNS 服务例外来使用更合理。


# 技术栈:Kubernetes + YAML 部署示例

# 只放行到 kube-system 中 coredns 服务的出站流量
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-dns-egress
  namespace: secure-business
spec:
  podSelector: {}
  policyTypes:
  - Egress
  egress:
  - to:
    - namespaceSelector:
        matchLabels:
          kubernetes.io/metadata.name: kube-system
      podSelector:
        matchLabels:
          k8s-app: kube-dns
    ports:
    - protocol: UDP
      port: 53
    - protocol: TCP
      port: 53

把这三段 YAML 组合起来,业务 Pod 只能访问 DNS 服务,其余外联必须手动加白,攻击者就算拿到 Shell,想向外传数据也会非常困难。

五、应用场景分析

这套临时加固方案最适合以下场景。

第一,线上集群节点数量多,重启窗口迟迟排不上,但漏洞等级又高到必须马上做点什么。第二,集群里的业务没有统一上 Service Mesh,短时间内无法改造。第三,公司安全合规要求必须留下风险处置记录,哪怕只是临时缓解措施也需要落地痕迹。第四,现有的安全团队人力少,没有时间去搭建复杂的运行时安全平台,必须用 Kubernetes 原生能力完成闭环。

这类方案不适合的场景也有,比如集群已经严重老化,很多 Pod 还在用特权模式跑,靠临时配置很难收敛;或者业务本身是高性能计算,对系统调用和内核特性要求特别高,加了 seccomp 跟白名单后性能暴跌,那就得考虑更专业的 livepatch 方案配合调度腾挪。

六、技术优缺点分析

优点非常明显。先用 Sysctl 和卸载模块就能在节点层面快速止血,不用动 Pod。再用 SecurityContext 和 Pod Security 收口业务自身权限,即使内核漏洞真实存在,攻击路径也被拉得很长。最后通过 NetworkPolicy 做网络围堵,就算前两层没拦住,数据也传不出去。整套方案不需要额外部署任何 Agent,不需要改镜像,也不需要重新编译内核,对现有业务侵入性最小。

缺点同样不容忽视。这些措施本质上都是缓解而非根除,它只是在漏洞和攻击者之间多砌了几堵墙,墙倒了问题依然在。而且安全策略收紧之后,部分老旧应用可能因为权限不足、目录只读、网络被禁等原因运行异常,需要逐一适配。Seccomp 白名单调优非常耗费时间,短期内不一定能覆盖所有业务。

七、注意事项

一定要把这次加固当成临时手段记录下来,设置一个明确的到期时间。别把临时安全配置混进长期基线,否则后续正式修复后,大家忘了清理这些屏蔽规则,变成一套谁也看不懂的过期策略。

修改 sysctl 的时候,要确认节点上的监控采集器依赖不依赖某些系统接口。比如 dmesg_restrict 改成 1 后,有些 node_exporter 版本的日志采集可能拿不到数据。严禁在大促期间做批量调整,至少先挑几个低峰节点灰度验证。

NetworkPolicy 千万不要一把梭直接全局默认拒绝,必须先从测试命名空间开始验证,观察几天再扩大范围。否则一个误配置,整个业务面全军覆没,比漏洞攻击还来得快。

对于内核漏洞本身,建议持续跟进厂商更新,推动业务方排期重启。热修复和加固只是续命手段,续不了太久,毕竟内核实实在在的缺陷还摆在那里,谁也不知道哪天就冒出来新的利用方式。最好的防守还是尽快升级内核,让系统回到一个干净版本。

八、文章总结

集群节点遇到内核漏洞,最理想的做法是立即重启升级,可真实生产环境里往往做不到说停就停。这种情况下,不必干瞪眼,也别硬着头皮往上冲。我们可以分两条腿走路:一条腿做操作层面的热修复,调整内核参数,卸载高风险模块;另一条腿在 Kubernetes 层收紧 Pod 权限,用 runAsNonRoot、只读根文件系统、capabilities drop、Seccomp 和 NetworkPolicy 把攻击路径堵得严严实实。

这套组合拳覆盖了事前降低触发概率、事中阻断提权路径、事后限制网络扩散的完整链路。所有措施都能在 Kubernetes 生态内实现,不需要引入复杂的第三方安全组件,普通团队完全有能力落地和维护。当然也要清醒认识到,这些动作换来的只是时间窗口。最终还是要推动节点重启和内核升级,让系统回到受支持的安全状态。

生产环境里没有一劳永逸的方案,只有把风险控制在自己能接受的范围内,然后持续逼近完美。希望这篇内容能在你真正遇到类似情况的时候,给你提供一个清晰可执行的参考。