在 Amazon EKS 上筑起四道隔离墙:ReadyOn 的多租户零信任实践

2026-09-19 34 预计阅读时间: 1 分钟
来源: aws.amazon.com AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:10 分钟

多租户平台最危险的假设,是把某一种隔离机制当成完整的安全边界。命名空间配置错误、Pod 调度失误、网络规则遗漏或数据库凭证泄露,都可能让单层防线失效。面对高度敏感的企业数据,ReadyOn 在 Amazon EKS 上采用了“四道墙”模型:同时使用 Kubernetes 命名空间、Karpenter 节点池、Amazon VPC 安全组和租户独享的 Amazon Aurora 数据库,构成相互独立的纵深防御。

这里的重点不是简单叠加四项 AWS 与 Kubernetes 功能,而是让每一层都能阻止上一层漏掉的问题。

四道墙分别挡住什么

第一堵墙:Kubernetes 命名空间

命名空间负责划分 Kubernetes API 对象,是租户隔离最自然的起点。每个租户拥有独立的 Deployment、Service、Secret、ServiceAccount 和资源配额,RBAC 权限也限制在对应命名空间内。

但命名空间本身并不是完整的安全沙箱。它不会自动阻止跨命名空间网络访问,也不能保证不同租户的 Pod 不会落到同一台节点。因此,这一层主要解决对象归属、权限范围和资源治理问题。

建议至少配套:

  • 为租户创建独立 ServiceAccount,避免共享默认身份;
  • 使用 Role 和 RoleBinding,而不是授予宽泛的 ClusterRole;
  • 设置 ResourceQuota 与 LimitRange,限制资源争抢;
  • 配置默认拒绝的 NetworkPolicy,作为集群内网络控制的补充;
  • 禁止租户工作负载使用 privileged、hostNetwork 和 hostPath 等高风险能力。

第二堵墙:Karpenter 节点池

ReadyOn 使用 Karpenter 节点池继续切分计算资源。租户工作负载通过标签、污点和调度约束进入指定节点池,避免不同租户的 Pod 默认混跑在同一台 EC2 实例上。

独立节点池能缩小容器逃逸、内核漏洞、节点资源耗尽和本地缓存残留等风险的影响范围。不过,污点和 nodeSelector 主要是调度机制,不能替代权限控制。平台必须限制谁能修改 Pod 的调度字段,也要防止租户自行添加其他租户的 toleration。

第三堵墙:Amazon VPC 安全组

网络层使用 Amazon VPC 安全组控制工作负载可以连接哪些服务。实践中,可以为租户工作负载关联独立安全组,并让租户 Aurora 数据库的入站规则只信任该安全组,而不是信任整个 VPC CIDR。

这种做法把控制点下沉到 AWS 网络层。即使某个 Pod 绕过 Kubernetes 层面的网络策略,它仍然需要通过安全组规则。反方向也一样:安全组配置错误时,命名空间、身份权限和数据库凭证仍应继续提供保护。

需要注意,EKS 中按 Pod 绑定安全组通常依赖 Amazon VPC CNI 的 Security Groups for Pods 能力,并受实例类型、网络模式和 CNI 配置影响,上线前必须核对当前集群支持情况。

第四堵墙:每租户独立的 Amazon Aurora 数据库

最后一道边界放在数据层。ReadyOn 为租户使用独立的 Amazon Aurora 数据库,而不是仅依赖共享数据库中的 tenant_id 字段。

这样可以减少应用查询遗漏租户条件、ORM 过滤器失效或数据库账号权限过宽带来的横向数据泄露。每个租户还应拥有独立凭证、备份策略、密钥和生命周期操作。即使应用层出现缺陷,一个租户的数据库身份也不应有能力读取另一个租户的数据。

独立数据库的代价是资源数量、连接管理、迁移编排和成本都会上升。因此,平台需要自动化租户开通、版本升级、凭证轮换和销毁流程,否则隔离越强,人工运维负担越重。

一套可以改造的 EKS 隔离骨架

下面的示例展示如何把命名空间、Karpenter 节点池和 Pod 安全组串起来。它是通用实践骨架,并不代表 ReadyOn 的完整生产配置。

运行前需要:

  1. sg-0123456789abcdef0 替换为租户工作负载使用的安全组;
  2. 确认集群已经安装 Karpenter,并存在名为 defaultEC2NodeClass
  3. 确认 Amazon VPC CNI 已启用 Security Groups for Pods;
  4. 根据集群中的 Karpenter 版本调整 API 版本。
apiVersion: v1
kind: Namespace
metadata:
  name: tenant-acme
  labels:
    tenant: acme
---
apiVersion: v1
kind: ResourceQuota
metadata:
  name: tenant-quota
  namespace: tenant-acme
spec:
  hard:
    requests.cpu: '8'
    requests.memory: 16Gi
    limits.cpu: '16'
    limits.memory: 32Gi
    pods: '50'
---
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
  name: tenant-acme
spec:
  template:
    metadata:
      labels:
        tenant: acme
    spec:
      taints:
        - key: tenant
          value: acme
          effect: NoSchedule
      requirements:
        - key: kubernetes.io/arch
          operator: In
          values: [amd64]
        - key: karpenter.sh/capacity-type
          operator: In
          values: [on-demand]
      nodeClassRef:
        group: karpenter.k8s.aws
        kind: EC2NodeClass
        name: default
  limits:
    cpu: '32'
---
apiVersion: vpcresources.k8s.aws/v1beta1
kind: SecurityGroupPolicy
metadata:
  name: tenant-acme
  namespace: tenant-acme
spec:
  podSelector:
    matchLabels:
      tenant: acme
  securityGroups:
    groupIds:
      - sg-0123456789abcdef0
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: tenant-api
  namespace: tenant-acme
spec:
  replicas: 2
  selector:
    matchLabels:
      app: tenant-api
      tenant: acme
  template:
    metadata:
      labels:
        app: tenant-api
        tenant: acme
    spec:
      nodeSelector:
        tenant: acme
      tolerations:
        - key: tenant
          operator: Equal
          value: acme
          effect: NoSchedule
      containers:
        - name: api
          image: public.ecr.aws/nginx/nginx:1.27
          resources:
            requests:
              cpu: 250m
              memory: 256Mi
            limits:
              cpu: 500m
              memory: 512Mi

保存为 tenant-acme.yaml 后执行:

kubectl apply -f tenant-acme.yaml
kubectl get pods -n tenant-acme -o wide
kubectl get nodes -L tenant,karpenter.sh/nodepool
kubectl describe securitygrouppolicy -n tenant-acme tenant-acme

数据库侧还需要建立对应关系:Acme 工作负载安全组只能访问 Acme Aurora 端点使用的安全组和端口;数据库凭证应放在 AWS Secrets Manager 等秘密存储中,再通过受控机制注入 Pod。不要把数据库密码直接写进 Deployment YAML,也不要让多个租户共用一个高权限数据库账号。

不要只验证“正常路径”

隔离系统的验收标准不应只是应用可以启动。更有价值的是持续运行负向测试:

# 检查租户 ServiceAccount 是否能读取其他命名空间的 Pod。
kubectl auth can-i list pods \
  --namespace tenant-beta \
  --as system:serviceaccount:tenant-acme:tenant-api

# 检查所有租户 Pod 实际落在哪些节点。
kubectl get pods -A -L tenant -o wide

# 查看节点的租户标签和所属 Karpenter NodePool。
kubectl get nodes -L tenant,karpenter.sh/nodepool

生产环境还应自动验证以下失败场景:

  • Acme 身份不能读取 Beta 命名空间中的 Secret;
  • Acme Pod 不能调度到 Beta 节点池;
  • Acme 安全组不能连接 Beta Aurora 端点;
  • Acme 数据库凭证不能登录 Beta 数据库;
  • 删除租户后,其节点、网络规则、凭证、备份和数据库都能按策略清理。

这些测试适合放进发布流水线和周期性安全作业中。仅靠人工审查 YAML,很难发现云端规则漂移或后续变更造成的隔离退化。

采用四道墙时的取舍

“四道墙”的价值在于独立失败:命名空间约束对象与身份,节点池隔离计算环境,安全组限制网络路径,Aurora 隔离最终数据。任何一层都不应默认信任前一层已经做对,这正是零信任式纵深防御的核心。

落地时可以按风险分级推进。高度敏感或受监管的租户使用完整的独立节点池、网络身份和 Aurora 资源;风险较低的租户则可以在明确接受风险的前提下共享部分基础设施。无论选择哪一级,都应把租户标识作为贯穿 Kubernetes、AWS 网络、数据库、日志和成本标签的统一键,并通过自动化防止配置漂移。

最终需要关注的不是“部署了多少安全产品”,而是一次配置错误最多能穿透几层边界,以及团队能否用可重复的测试证明租户之间确实无法横向移动。


相关推荐