跳过正文
AWS EKS 生产实践:网络、安全与多集群管理

AWS EKS 生产实践:网络、安全与多集群管理

·792 字·4 分钟·
目录

管理多套 EKS 集群两年下来,从最初踩的 IP 地址耗尽、IRSA 配置错误,到后来系统化做多账号隔离和成本控制,积累了一些不在官方文档里的实战心得。本文尽量绕开基础概念,聚焦在生产环境实际遇到的决策和问题。

网络选型:VPC CNI vs Cilium
#

EKS 默认使用 AWS VPC CNI,每个 Pod 直接分配 VPC IP,优点是网络拓扑简单、与 AWS 原生服务(ALB、Security Group for Pods)无缝集成。但有一个致命问题:IP 地址消耗极快

一个 m5.xlarge 节点(4 vCPU)最多能挂 4 个 ENI,每个 ENI 最多 15 个 IP,理论上限 58 个 Pod。但实际上,Daemonset(node-exporter、fluentd、karpenter 等)会占掉 8-10 个,真正可用的 Pod 槽位远少于理论值。

更大的问题是子网规划。如果初期给节点子网划了 /24(254 个 IP),加上节点本身的 IP,撑不了多少 Pod。我们有一套集群初期规划不足,后来迁移子网花了将近一周。

IP 地址规划建议:

  • 节点子网至少 /22(1022 个 IP),大规模集群用 /20
  • 如果 VPC 地址空间紧张,考虑开启 VPC CNI 的 ENABLE_PREFIX_DELEGATION,一个 ENI 可分配 /28 前缀(16 个 IP),大幅提升密度
# 检查节点当前 IP 使用情况
kubectl get node -o json | jq '.items[] | {name: .metadata.name, allocatable: .status.allocatable["vpc.amazonaws.com/pod-eni"]}'

# 开启前缀委派
kubectl set env daemonset aws-node -n kube-system ENABLE_PREFIX_DELEGATION=true
kubectl set env daemonset aws-node -n kube-system WARM_PREFIX_TARGET=1

Cilium 适合什么场景: 如果需要复杂的 L7 网络策略、eBPF 可观测性,或者想绕开 VPC CNI 的 IP 限制,Cilium 是合理选项。但它需要替换掉 kube-proxy,迁移成本高,而且与 AWS 原生 LB Controller 的集成需要额外配置。我们目前的生产集群没有做这个切换,仍用 VPC CNI + Security Group for Pods 的组合。

IAM for Service Account(IRSA)
#

IRSA 是 EKS 上 Pod 访问 AWS 资源的推荐方式。原理是 EKS 集群有一个 OIDC Provider,Pod 的 Service Account 携带一个 OIDC token,AWS STS 验证这个 token 并换取临时凭证。

配置步骤很清晰,但有几个坑:

# 1. 确认集群已关联 OIDC Provider
aws eks describe-cluster --name my-cluster --query "cluster.identity.oidc.issuer" --output text

# 2. 创建 OIDC Provider(只需一次)
eksctl utils associate-iam-oidc-provider --cluster my-cluster --approve

# 3. 创建 IAM Role,Trust Policy 指向特定 SA
OIDC_PROVIDER=$(aws eks describe-cluster --name my-cluster \
  --query "cluster.identity.oidc.issuer" --output text | sed 's|https://||')

cat > trust-policy.json <<EOF
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": {
      "Federated": "arn:aws:iam::123456789012:oidc-provider/${OIDC_PROVIDER}"
    },
    "Action": "sts:AssumeRoleWithWebIdentity",
    "Condition": {
      "StringEquals": {
        "${OIDC_PROVIDER}:sub": "system:serviceaccount:production:my-app",
        "${OIDC_PROVIDER}:aud": "sts.amazonaws.com"
      }
    }
  }]
}
EOF

常见踩坑:

  1. Trust Policy 的 namespace 写错system:serviceaccount:<namespace>:<sa-name> 中的 namespace 必须与 Pod 实际运行的 namespace 一致,大小写敏感。
  2. Pod 启动后 SA 的 annotation 没生效:annotation eks.amazonaws.com/role-arn 必须在 SA 上,不是 Pod 上。修改 SA 后已有的 Pod 不会自动更新 token,需要重启。
  3. 跨账号 assume role:如果需要访问另一个账号的资源,要在目标账号的 Role 上额外加信任,允许源账号的 IRSA Role 来 assume。
# Service Account 配置示例
apiVersion: v1
kind: ServiceAccount
metadata:
  name: my-app
  namespace: production
  annotations:
    eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/my-app-role
    eks.amazonaws.com/token-expiration: "86400"  # token 有效期,默认 86400s

节点组 vs Karpenter
#

这是 EKS 集群架构的核心决策之一。

Managed Node Group 的优势是稳定、AWS 负责底层生命周期管理、节点升级时 AWS 会自动做 drain。但它是静态的,你需要手动或通过 Cluster Autoscaler 来扩缩,而 Cluster Autoscaler 的扩容逻辑是"有 Pending Pod 才扩",且每次只扩一个节点,速度慢。

Karpenter 的核心优势:

  • 看 Pod 的实际资源需求选最合适的实例类型,而不是固定实例类型
  • 支持 Consolidation,主动合并利用率低的节点
  • 不依赖 ASG,直接调 EC2 API,扩容速度快很多

我们的做法是混用:核心基础设施组件(ArgoCD、监控、日志)放在 Managed Node Group 上保证稳定性,业务工作负载全部交给 Karpenter 管理。

# Karpenter NodePool 示例
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
  name: general
spec:
  template:
    metadata:
      labels:
        workload-type: general
    spec:
      nodeClassRef:
        group: karpenter.k8s.aws
        kind: EC2NodeClass
        name: default
      requirements:
        - key: karpenter.sh/capacity-type
          operator: In
          values: ["spot", "on-demand"]
        - key: karpenter.k8s.aws/instance-category
          operator: In
          values: ["c", "m", "r"]
        - key: karpenter.k8s.aws/instance-generation
          operator: Gt
          values: ["4"]
      expireAfter: 720h  # 30天强制轮换节点
  disruption:
    consolidationPolicy: WhenEmptyOrUnderutilized
    consolidateAfter: 1m
  limits:
    cpu: "200"
    memory: 400Gi

多账号多集群访问管理
#

生产环境通常有 dev/staging/prod 多个账号,每个账号下可能还有多个集群(us-west-2、ap-southeast-1)。kubeconfig 管理如果不规范,很容易误操作到错误集群。

# 标准化添加集群到 kubeconfig,profile 对应 AWS 账号
aws eks update-kubeconfig \
  --region us-west-2 \
  --name prod-us \
  --alias prod-us \
  --profile prod-account

aws eks update-kubeconfig \
  --region ap-southeast-1 \
  --name prod-cn \
  --alias prod-cn \
  --profile prod-account

# 列出所有 context
kubectl config get-contexts

# 强制指定 context,避免依赖当前默认 context(在脚本里尤其重要)
kubectl --context=prod-us get nodes

防止误操作的实践:

.zshrc 里加一个 prompt 显示当前 context:

# 在 PS1 里加入 k8s context
parse_k8s() {
  kubectl config current-context 2>/dev/null | sed 's/.*\///'
}
export PS1='$(parse_k8s) $ '

另外,对于 prod 集群的写操作,我们用 kubectl--dry-run=server 先验证,再执行。

EKS 集群升级策略
#

EKS 每年发布约 3 个 K8s 小版本,每个版本支持 14 个月,到期前必须升级,否则会被强制升级。

升级前检查清单:

# 1. 检查 deprecated API(重要!K8s 1.25 移除了 PodSecurityPolicy)
kubectl get --raw /apis | jq '.groups[].preferredVersion.version' | sort -u

# 2. 用 pluto 扫描 deprecated API
pluto detect-all-in-cluster --target-versions k8s=v1.31.0

# 3. 检查 add-on 版本兼容性
aws eks describe-addon-versions --kubernetes-version 1.31 \
  --query 'addons[].{Name:addonName,Versions:addonVersions[0].addonVersion}'

升级顺序:

  1. 升级 Control Plane(AWS 管理,约 10 分钟)
  2. 升级 CoreDNS、kube-proxy、VPC CNI 等 managed add-ons
  3. 升级节点(Managed Node Group 做滚动更新,或 Karpenter 通过 expireAfter 自然轮换)

对于生产集群,我们优先用 blue-green 升级:在同一个 VPC 里建新版本集群,迁移工作负载,而不是 in-place 升级。成本高一些,但风险可控,出问题可以立刻切回去。

安全加固
#

Pod Security Admission
#

K8s 1.25 移除 PSP 后,PSA(Pod Security Admission)是内置替代方案。我们对不同 namespace 设置不同的安全级别:

# namespace 标签控制 PSA 策略
apiVersion: v1
kind: Namespace
metadata:
  name: production
  labels:
    pod-security.kubernetes.io/enforce: restricted
    pod-security.kubernetes.io/enforce-version: latest
    pod-security.kubernetes.io/warn: restricted
    pod-security.kubernetes.io/audit: restricted

restricted 策略要求容器不能以 root 运行、不能挂载 hostPath、必须设置 securityContext。大部分业务应用改起来不难,麻烦的是一些老的基础设施组件(如某些日志收集器)需要 root,要单独给它们的 namespace 设 privilegedbaseline

Network Policy
#

默认 EKS 集群 Pod 之间全通。Network Policy 是 namespace 级别的 L4 防火墙:

# 只允许来自同 namespace 的流量,以及 monitoring namespace 的 Prometheus 抓取
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-ingress
  namespace: production
spec:
  podSelector: {}
  policyTypes:
    - Ingress
  ingress:
    - from:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: production
    - from:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: monitoring
      ports:
        - protocol: TCP
          port: 8080  # metrics port

注意:VPC CNI 默认支持 Network Policy,但需要确认 aws-node daemonset 开启了 NETWORK_POLICY_ENFORCING_MODE

成本优化
#

Spot 实例混用策略:

Karpenter 的 capacity-type 同时包含 spoton-demand,Karpenter 会优先尝试 Spot。但有几点要注意:

  • 有状态服务(如带本地 PV 的组件)不要跑 Spot
  • topologySpreadConstraints 分散 Pod,避免同一个节点被中断时影响过大
  • Spot 中断前 2 分钟会有通知,Karpenter 会自动处理(drain + 起新节点)
# Pod 配置 Spot 容忍
spec:
  tolerations:
    - key: karpenter.sh/capacity-type
      operator: Equal
      value: spot
      effect: NoSchedule
  topologySpreadConstraints:
    - maxSkew: 1
      topologyKey: topology.kubernetes.io/zone
      whenUnsatisfiable: DoNotSchedule
      labelSelector:
        matchLabels:
          app: my-app

Savings Plans: 对于 on-demand 节点,Compute Savings Plans 可以省 40-60%。按过去 3 个月的实际用量承诺基线,Peak 流量超出的部分走按需计费。我们买的是 1 年期 Compute Savings Plans,不绑定实例类型,灵活性最高。

实际下来,EKS 集群的 EC2 成本通过 Spot + Karpenter consolidation + Savings Plans 三管齐下,相比初期纯 on-demand 节点组的方案,降低了约 55%。

小结
#

EKS 生产化没有捷径,很多问题只有在规模上来之后才会暴露。IP 地址规划要在一开始做对,因为后期改很痛苦。IRSA 权限要最小化,每个服务一个专用 Role。升级要有 SOP,不要等到最后期限才动手。安全策略要分层,不要指望一个工具解决所有问题。

Wenzhuo Huang
作者
Wenzhuo Huang
搞运维的工程师,写代码的运维人。专注 Kubernetes、AWS、GitOps 与基础设施可靠性。这个博客既是我的技术笔记本,也是我踩过的坑的受害者档案。

相关文章

云原生转型实践:从传统运维到 K8s 的迁移经验

·653 字·4 分钟
这是一篇个人经验向的文章,记录了从传统虚拟机运维转向 Kubernetes 的全过程:为什么要迁移、迁移中踩了哪些坑、团队如何度过学习曲线,以及回头看哪些事情当时做对了。

Kubernetes NetworkPolicy 网络隔离实战

·2505 字·12 分钟
系统讲解 Kubernetes NetworkPolicy 的工作机制与生产实战配置,覆盖 deny-all 基础模板、常见隔离场景、Cilium 扩展、多租户设计、测试验证方法及常见陷阱。