上个月帮一位朋友搭环境,他手里有一批业务要从 EC2 手动部署迁到 Kubernetes,团队里没有专职集群运维,问我到底是买托管服务还是自己用 kubeadm 搭。我直接建议用 AWS EKS——Amazon Elastic Kubernetes Service。用 EKS 最大的好处是控制面由 AWS 负责,apiserver、etcd、scheduler 这些组件不需要你操心,你只需要管节点和应用,集群本身又是标准 Kubernetes,社区工具和生态都能直接用。这篇文章没有废话,从零开始带你创建 EKS 集群,包括前置准备、ClusterConfig 模板、节点组扩容、kubectl 配置,再深入讲 Namespace 隔离、Redis 集群部署、GPU 调用,最后把生产环境最容易踩的坑和排查思路一并整理出来。不管你是刚学 K8s 的新手,还是在自建集群和托管集群之间犹豫的老手,这篇都可以当作一份能直接照做的手册。
1. 为什么我推荐用 EKS:托管和自建的差别真的巨大
1.1 控制面托管省下的时间成本
先把概念理清楚。很多人会把 Docker 和 K8s 混在一起问,Docker 是容器运行时,解决单个容器怎么打包、怎么启动的问题;K8s 是容器编排平台,解决成千上万个容器怎么调度、怎么保证服务可用性、怎么做滚动更新的问题。AWS EKS 则是 K8s 的托管版本,你给它一个 VPC 区域和节点组,它就帮你把 K8s 控制面全部拉起来。
如果你的集群是自建的,需要准备至少三台 master 节点跑 apiserver、controller-manager、scheduler、etcd,光一个 etcd 的备份、恢复、高可用就够你喝一壶。控制面的证书过期、etcd 磁盘空间被打满、apiserver 被大流量请求拖垮,这些故障每一个都可能把业务环境搞挂。EKS 把这些风险全部承接过去,AWS 负责控制面的高可用、证书轮换、etcd 备份,你只需要把精力放到节点组和业务负载上。
1.2 EKS 集群的组成和账单有哪些
EKS 集群从视角上看分成三部分:控制面、节点组、附加组件。
控制面就是你执行 kubectl 看到的那个 apiserver endpoint,它分布在所选区域内至少两个可用区,AWS 会自动管理。节点组是真正运行 Pod 的 EC2 实例,可以是 Managed Node Group,也可以自带节点到集群。附加组件包括 CoreDNS、VPC CNI、kube-proxy,EKS 默认会装上,也可以自定义版本。
计费方面,集群本身是每小时收一定的控制面费用,具体金额以 AWS 官方价格页为准。节点组里的 EC2 实例按普通的 EC2 计费,EBS 卷、EIP 等资源另外算钱。没有免费的 EKS 集群,哪怕你一台节点都不加,只要控制面在跑,它就在计费。所以实验做完一定要记得删除集群,否则钱包会替你记仇。
1.3 什么场景不适合 EKS
EKS 不是银弹。如果你的场景是离线环境、机房自建,网络到不了 AWS,那显然没法用。如果团队想深入掌握 Kubernetes 的每一层原理,控制面被托管之后反而看不到内部细节。还有成本敏感的小实验环境,长期跑一个自建的单节点集群,成本可能比 EKS 更低。
我列一张简单的对比表,方便你按需判断:
| 对比维度 | 自建 K8s | AWS EKS |
|---|---|---|
| 控制面维护 | 自己部署、监控、备份 | AWS 托管,自动高可用 |
| 初始门槛 | 需要 3 台 master + 容器运行时调优 | eksctl 一条命令拉起 |
| 学习价值 | 高,能彻底理解 K8s 内部机制 | 中等,更多关注业务负载 |
| 生产可用性 | 需要大量运维投入 | 天然设计为生产级别 |
| 成本 | 只有 EC2 成本,但维护成本高 | 每小时控制面费 + EC2 成本 |
所以我的观点是:如果你是为了快速上线业务,EKS 是更划算的选择;如果你是为了学习 K8s,一定要找个自建集群练练手,但别在生产环境拿自建硬扛。
2. 创建集群前,这些准备工作一个都不能省
2.1 工具链安装:awscli、eksctl、kubectl
创建 EKS 最常用的工具就三个:AWS CLI、eksctl、kubectl。很多教程是从 AWS console 点出来的,但点控制台容易漏细节,而且不好重复执行。我更推荐用 eksctl 命令行的方式,它是 AWS 官方推荐的集群创建工具,底层用 CloudFormation 帮你生成资源,出错的时候可以通过 CloudFormation 日志精确排错。
安装方式按你的系统来。macOS 方便一点:
brew install awscli brew install kubectl brew install eksctlLinux 上如果没有包管理器,可以下载二进制丢到 /usr/local/bin:
curl --silent --location "https://github.com/eksctl-io/eksctl/releases/latest/download/eksctl_$(uname -s)_amd64.tar.gz" | tar xz -C /tmp sudo mv /tmp/eksctl /usr/local/bin装完顺手验证一下版本:
aws --version eksctl version kubectl version --client这里有个容易忽略的小坑:kubectl 的客户端版本最好不要太老,否则有些新的资源字段没法解析。我建议使用和集群版本接近的客户端版本,比如集群是 1.30,就用 1.30 或更新的 kubectl。
2.2 IAM 权限模型和角色
EKS 的角色体系分三层:集群 IAM 角色、节点实例角色、以及执行 kubectl 的用户权限。
集群 IAM 角色是让 EKS 控制面可以调用 AWS API 创建负载均衡器、安全组等资源。这个角色需要信任 eks.amazonaws.com,并且绑定 AmazonEKSClusterPolicy。
节点实例角色是让 EC2 节点能向控制面注册,从 ECR 拉镜像,使用弹性网卡和安全组。一般给节点绑定 AmazonEKSWorkerNodePolicy、AmazonEKS_CNI_Policy、AmazonEC2ContainerRegistryReadOnly 这几个托管策略。
创建 EKS 的 AWS 用户还需要 eks:CreateCluster、eks:CreateNodegroup、iam:CreateRole、iam:PassRole 等权限。如果你不是管理员账号,建议让管理员直接给你一个最小权限的 IAM Policy,省得到创建一半报 AccessDenied。
用 eksctl 创建集群时,它会自动帮你创建第二个角色,不需要你去 IAM 手工开。但如果你想用现有角色,在 ClusterConfig 里可以指定 iam 配置,生产审计要求严格的团队通常这么做。
2.3 VPC 和子网设计
EKS 对 VPC 的要求比普通 EC2 严格得多。控制面创建的时候,你要给它指定至少两个可用区的子网,每个子网必须带特定标签,不然节点组会注册失败。
如果你用 eksctl 默认方式创建,它会新建一个 VPC,自动分配 CIDR 和子网,并且打上正确的标签,省心很多。但生产环境通常要用已有的 VPC,就必须手动确认子网标签存在:
kubernetes.io/cluster/eks-demo: owned kubernetes.io/role/elb: "1"第一个标签是让 EKS 识别子网归属,第二个标签是让 AWS Load Balancer 找到公网子网来部署负载均衡器。如果你只给私有子网跑节点,还需要确认 Pod 使用的网段不会和 VPC 网段冲突。默认的 VPC CNI 会给 Pod 分配 VPC 内空闲 IP,如果 VPC 地址规划太紧张,集群一大就容易出现 IP 耗尽。
另外,创建 VPC 时要开启 DNS Hostnames 和 DNS Resolution,否则节点解析 apiserver 域名会失败,这个问题我在排查节点加入失败时遇到不止一次。
2.4 账号配额和后付费确认
创建集群之前最容易被忽视的是账号配额。比如一个区域最大 EC2 实例数量只有 20,如果你选择 3 个节点组、每组 3 台 t3.large,可能直接超过配额导致创建失败。还有 VPC 数量、弹性 IP 数量都有默认配额,建议提前到 Service Quotas 页面检查。
同时确认账号已经正确关联支付方式。EKS 控制面、EC2 节点都是后付费,如果账号欠费,创建到一半直接被停掉,清理起来比创建更麻烦。
3. EKS 集群创建实录:一条命令并不神秘
3.1 编写可复用的 ClusterConfig
我习惯用 ClusterConfig 文件管理集群配置,而不是直接敲一大堆参数。它像是一份“基础设施即代码”的模板,后续改节点组、加 GPU 节点只要改 YAML 再执行就行。
下面是一份我在实验环境常用的模板:
apiVersion: eksctl.io/v1alpha5 kind: ClusterConfig metadata: name: eks-demo region: ap-southeast-1 version: "1.30" vpc: cidr: 192.168.0.0/16 managedNodeGroups: - name: workers instanceType: t3.large desiredCapacity: 3 minSize: 2 maxSize: 6 privateNetworking: true ssh: allow: true publicKeyName: my-key解释几个关键字段:
version填你希望创建的 K8s 版本,创建前先到 EKS 支持的版本列表里确认。vpc.cidr是自定义 VPC 网段,如果你不写,eksctl 会用默认 192.168.0.0/16。managedNodeGroups表示托管节点组,由 EKS 自动管理 Auto Scaling 和节点更新。privateNetworking: true让节点只使用私有子网,公网访问通过负载均衡器出去,安全性和网络成本都更好。
之后执行:
eksctl create cluster -f cluster.yaml执行过程大约 15 到 20 分钟。它会先创建一个 CloudFormation 栈,里面包含 VPC、IAM、控制面和节点组。中途会输出类似waiting for the control plane to become ready的日志。如果创建失败,到 CloudFormation 控制台看具体哪一个资源报错,是角色权限还是网络配置,定位非常直接。
3.2 集群创建后的健康检查
创建完不要急着部署应用,先做一轮基本健康检查。
eksctl get cluster --region ap-southeast-1 kubectl get nodes正常情况下你会看到节点处于 Ready 状态。再看控制面相关的 Pod 是否正常:
kubectl get pods -n kube-systemkube-system 里通常有 aws-node、coredns、kube-proxy,这些 Pod 的 READY 应该都是 1/1。如果 coredns 一直 Pending,八成是节点资源不足或者安全组阻止了 DNS 流量。
这里要说明一下:EKS 是托管控制面,你不会在 EKS 里看到 kube-controller-manager、etcd 这些 Pod,它们运行在 AWS 托管的组件里。所以如果你看到网上有人用一个命令初始化 Kubernetes master,然后报出the api server is not healthy after 4m0.00747357s,那是在自建 kubeadm 环境里的情况,EKS 控制面不会出现这个报错。
3.3 节点组扩容和加 GPU 节点
业务量涨了怎么办?托管节点组支持两种方式扩容:手动调整期望数量,或者启用 Cluster Autoscaler。
手动扩缩容只要一条命令:
eksctl scale nodegroup --cluster eks-demo --name workers --nodes 5这是把 desiredCapacity 调整为 5。如果你没有指定--nodes-max,实际节点组里还能靠 ASG 自动扩张。
想加一个 GPU 节点组,比如跑推理任务,就在 ClusterConfig 里再加一组:
- name: gpu-workers instanceType: g4dn.xlarge desiredCapacity: 1 minSize: 1 maxSize: 2 privateNetworking: true然后执行:
eksctl create nodegroup --config-file cluster.yaml这里需要注意,GPU 节点组必须使用 EKS 的 GPU 优化 AMI,eksctl 默认会选对。如果你想手动给节点打标签,可以在 nodeGroup 里加 labels 字段,比如accelerator: nvidia,后续调度 GPU 任务会用到。
3.4 如果创建到一半失败了怎么办
创建失败大概率是角色权限、子网标签、配额、以及安全组规则的问题。第一反应应该是看 CloudFormation 事件。
aws cloudformation describe-stack-events \ --stack-name eksctl-eks-demo-cluster \ --region ap-southeast-1查看RESOURCE_STATUS_REASON字段,里面通常会直接告诉你哪个资源失败,比如VPC does not have DNS hostname attributes enabled。修复之后再重新执行创建命令。
删除失败的集群不要直接在 console 里东删一个西删一个,用命令清理干净:
eksctl delete cluster --name eks-demo --region ap-southeast-1它会连带删除 CloudFormation 栈里的负载均衡器、数据库,避免留下孤儿资源继续计费。
4. 从创建到上线:部署应用、隔离、Redis 与 GPU
4.1 kubectl 配置和验证
用 eksctl 创建集群成功之后,它会自动把 kubeconfig 写到默认位置,但如果你在别的机器上想连接集群,可以用 AWS CLI 重新生成:
aws eks update-kubeconfig --region ap-southeast-1 --name eks-demo --alias eks-demo然后执行kubectl get namespace,如果能看到 kube-system、default、kube-public,说明连接成功了。在环境变量或 kubeconfig 里设置 context 别名的好处是,多个集群切换的时候不会迷路。
实际操作中我发现很多人连不通是因为 IAM 用户没有eks:DescribeCluster权限,执行 update-kubeconfig 时报 AccessDenied。这个问题只要给用户加上 eks 相关只读权限就行,不用太复杂。
4.2 用 Namespace 做环境隔离
K8s 里很基础也很重要的一个概念就是 Namespace。很多人会把 Namespace 当成环境隔离的唯一手段,但它其实是逻辑隔离,不是硬隔离。比如你可以创建 dev、staging、prod 三个 Namespace,让业务资源各归各的,但底层网络和计算资源还是共享的。
在 EKS 里创建 Namespace 直接一条命令:
kubectl create namespace dev kubectl create namespace prod但我更建议用文件方式管理,方便审查:
apiVersion: v1 kind: Namespace metadata: name: dev --- apiVersion: v1 kind: ResourceQuota metadata: name: dev-quota namespace: dev spec: hard: requests.cpu: "4" requests.memory: 8Gi limits.cpu: "8" limits.memory: 16Gi pods: "10"ResourceQuota 是 Namespace 隔离的关键,没有配额机制,一个 Namespace 里的业务很容易把整个集群的资源吃光。我一般会给 dev、staging、prod 配不同的配额,再配合 LimitRange 设置默认 requests 和 limits,这样哪怕开发者忘写资源请求,也不会裸奔。
4.3 部署一个 Web 服务并暴露到公网
部署一个简单的 Nginx 服务,感受一下完整的应用上线路径。
先创建 Deployment:
apiVersion: apps/v1 kind: Deployment metadata: name: nginx-demo namespace: prod spec: replicas: 3 selector: matchLabels: app: nginx-demo template: metadata: labels: app: nginx-demo spec: containers: - name: nginx image: nginx:1.27-alpine ports: - containerPort: 80 resources: requests: cpu: 100m memory: 128Mi limits: cpu: 500m memory: 256Mi执行kubectl apply -f nginx-demo.yaml之后,Service 用 LoadBalancer 类型暴露出来:
apiVersion: v1 kind: Service metadata: name: nginx-demo-svc namespace: prod spec: selector: app: nginx-demo ports: - port: 80 targetPort: 80 type: LoadBalancerEKS 会自动创建一个 AWS 负载均衡器,等kubectl get svc -n prod看到 EXTERNAL-IP 变成实际地址,就可以访问了。这个方案适合单个服务暴露,如果服务多了,建议装 AWS Load Balancer Controller,用 Ingress 统一管理路由和 TLS 证书。
4.4 在 EKS 上部署 Redis Cluster
很多人问 Redis 能不能跑在 K8s 里,答案当然能,而且 EKS 上跑 Redis Cluster 是很典型的场景。我这里的方案是使用 StatefulSet,给每个 Redis 实例一个稳定的网络标识和独立存储卷。
三个 master 节点的 Redis Cluster 最小结构是:3 个 StatefulSet 副本,每个副本配一个 headless service,让 Pod 之间可以通过类似redis-0.redis-headless.prod.svc.cluster.local的域名互相发现。
关键文件片段:
apiVersion: apps/v1 kind: StatefulSet metadata: name: redis-cluster namespace: prod spec: serviceName: redis-headless replicas: 6 selector: matchLabels: app: redis-cluster template: metadata: labels: app: redis-cluster spec: containers: - name: redis image: redis:7-alpine command: - /bin/sh - -c - "redis-server --cluster-enabled yes --cluster-config-file /data/nodes.conf --cluster-node-timeout 5000 --appendonly yes"记得挂载 PVC,不然节点重建秒变空库。Redis 的 Cluster 初始化也很有意思,需要先用redis-cli --cluster create把所有节点的 IP 或域名加进去,形成槽位分配,之后才能真正执行 KV 请求。
我踩过的坑是 headless service 的端口没有定义 clusterIP: None,或者选举用的 port 没有暴露,导致节点间找不到彼此。这种问题用redis-cli --cluster check能很快定位到时哪个节点缺席。
4.5 让 K8s 成功调用 GPU
跑 AI 推理、模型训练,核心诉求是让 K8s 的调度器感知 GPU 资源。在 EKS 上要分三步走。
第一步,创建 GPU 节点组,实例类型选择g4dn.xlarge或者更高型号。这个在 3.3 里提到过,不再重复。
第二步,安装 NVIDIA device plugin,让节点上的 GPU 数量以扩展资源的形式暴露给 K8s。官方仓库有一条现成的 DaemonSet,执行:
kubectl apply -f https://raw.githubusercontent.com/NVIDIA/k8s-device-plugin/v0.15.0/nvidia-device-plugin.yml安装完成后,查看节点资源:
kubectl describe node g4dn-instance你应该能看到nvidia.com/gpu: 1这样的可调度资源。
第三步,在应用里声明 GPU 请求:
resources: limits: nvidia.com/gpu: 1调度器看到这个字段,就会把 Pod 调度到有 GPU 的节点上。EKS 的 GPU 节点组一般预装了 NVIDIA 驱动,如果你用的是自定义 AMI,还需要自己处理驱动和 ec2 实例类型匹配的问题。
5. 那些年我踩过的 EKS/K8s 故障坑
5.1 API Server “not healthy” 到底是怎么回事
很多人在学习 K8s 初始化时,会在 kubeadm init 阶段看到这个报错:
the api server is not healthy after 4m0.00747357s
我为什么对这个时间戳印象深,因为第一次在 Rocky 系统上自建集群,就卡在这里彻底崩了一次。这个问题几乎都和 kubelet 或容器运行时有关,特别是 containerd 的配置没跟上对应 K8s 版本的 pause 镜像。常见排查路径是:
systemctl status kubelet containerd journalctl -xeu kubelet | tail -200 crictl ps -a重点看 containerd 报的image pull failed,如果 pause 镜像版本不匹配,就把 sandbox_image 改掉再重启 containerd。说实话,这类问题让你难受,但你解一遍之后,对 K8s 控制面的理解会比原来深很多。
在 EKS 里不会看到这个错误,因为控制面是 AWS 管的。但如果你自己搭的节点访问不到 EKS 的 apiserver,会看到节点状态 NotReady,kubelet 日志里一直报连接超时。这种时候优先检查安全组,节点到 443 端口是否放行,再看节点上的 aws-vpc-cni 是否正常。
5.2 节点组加入失败的几个隐蔽原因
节点组已经创建,但节点一直没有 NotReady,是最头疼的。我排查过太多现场,发现几个高频原因。
子网标签缺失是第一大坑。EKS 是靠子网上的kubernetes.io/cluster/<cluster-name>标签来识别能不能用于集群的。如果你用的是已有 VPC,没打标签,节点组创建出来了,但控制面不认子网,自然注册不了。
第二个是节点实例角色的权限不够。如果你只绑定了一个AmazonSSMManagedInstanceCore,而漏掉了AmazonEKSWorkerNodePolicy,节点无法从控制面拿到调度配置,就一直卡在初始化。
第三个是节点所在的安全组把 kubelet 到 apiserver 的 443 端口挡了。很多企业安全策略默认只开放 80/443/3389,结果 kubelet 连接控制面被拦死。解决方法是把控制面安全组加到节点安全组的入方向白名单里。
5.3 Pod 起不来、老被驱逐怎么办
Pod 启动不起来最典型的场景是 Pending。执行kubectl describe pod的时候,Events 部分会把原因写清楚。最常见的是Insufficient cpu/memory,说明节点资源不够,或者你的 requests 设得太高。
还有一种是FailedScheduling,因为没有节点满足 nodeSelector、亲和性、或者容忍度要求。比如你部署 GPU 工作负载,Pod 明明声明了 nvidia.com/gpu,但集群里没有 GPU 节点,调度器当然不会给面子。
Pod 被驱逐要分情况看待。如果节点本身内存压力高,kubelet 会率先驱逐使用量超过 limits 的容器。生产环境里常见的故障就是开发者忘了设置 requests 和 limits,某一个容器疯狂吃内存,触发整节点 OOM,然后所有 Pod 被拖累。解决方式不复杂:所有 workload 都要写资源配额,再按 HPA 或 Cluster Autoscaler 应对增量。
5.4 生产故障怎么影响用户的,我的一次真实 CPU 吃满事故
有一回我接手一个 EKS 集群,线上用户投诉服务白屏,打开监控发现节点 CPU 已经无限接近 100%。原因是多个 Java 服务共享一个节点组,而它们的 Deployment 全部没有设置 CPU limits,G1 GC 在某些版本下会疯狂占 CPU,节点很快被打穿,Pod 全部进入 CPUThrottled 状态,延迟飙升。
当时排查的顺序是:先用kubectl top nodes看节点负载,再用kubectl top pods -n prod --sort-by=cpu找到吃 CPU 的 Pod,最后把服务拆到独立的节点组,并加了如下配置:
resources: requests: cpu: 500m memory: 512Mi limits: cpu: "2" memory: 1Gi这个事给我的教训是:节点资源不是无限的,应用一定要设置 requests 和 limits;同时生产集群必须搭配监控和告警,像kubectl top只是临场急救,真正的容量规划要靠 Prometheus 的数据积累才能提前发现风险。
5.5 Rocky 上自建 K8s 的翻车记录
这个话题和 EKS 无关,但很多人在学习 K8s 时都会经历从 EKS 到自建的跨越,所以还是写一下。朋友在 Rocky Linux 上用 kubeadm 安装某个新版本 K8s,集群初始化之后同样报了the api server is not healthy。我远程过去看,发现 containerd 启动正常,但 kubelet 一直看不到静态 Pod,最终查出来是 containerd 的 sandbox_image 拉取 pause 镜像失败。
解决方法是修改/etc/containerd/config.toml里的sandbox_image,换成与当前 K8s 版本兼容的 pause 镜像地址,然后重载 containerd,清理临时文件,再重新 reset 后 init。自建集群就要接受这种时不时折腾一下的事实,但这也反过来提醒我,EKS 托管控制面的价值不只是省时间,更是把这类基础环境的稳定性外包给 AWS 的 SRE。
6. 学习 K8s 的资料和环境建议
6.1 别只囤 PDF,动手练起来
网上经常有人找《K8s 权威指南》的 PDF,我应该也翻过老版本,内容确实系统,但这本书再厚也只是帮你建立概念,真正学会 K8s 只有一条路:自己在真实集群里反复打命令。我建议照着这篇教程建一个 EKS 集群,然后立刻开始跑 Deployment、Service、StatefulSet,再故意删掉几个 Pod 看看发生了什么,体会一下什么叫自愈。
做实验的时候可以把每天用到的命令记成学习笔记。比如记录kubectl get events --sort-by=.lastTimestamp怎么看调度事件,或者kubectl rollout status deployment/nginx-demo怎么等发布完成。这些笔记积累起来,比任何一本教程都有用。
6.2 把 EKS 理解为 K8s 的“套餐”
用 EKS 越久越发现,托管服务虽然方便,但它只是 You 获得标准 K8s 的一种途径。如果你只会用 eksctl 创建集群,不理解 master 节点在做什么,遇到奇奇怪怪的网络问题还是会懵圈。我建议学完 EKS 之后,用 kubeadm 在虚拟机或者本地环境自建一个单节点集群,体验一下证书生成、etcd 启动、kubelet static Pod 这些过程。
等碰到 kubeadm init 报not healthy这些错误时,别烦躁,那是在给你补课。每一类报错背后都隐藏着 K8s 某个机制的原理,解决了它,你对集群的理解才算真正到位。
我现在电脑里还存着第一次创建 EKS 集群时用的 cluster.yaml,每次打开都能想起那天晚上反复删槽、重建、等状态的折腾。如果你正准备创建自己的第一个 EKS 集群,希望这篇教程能帮你少踩几个坑。记住,创建成功只是开始,监控、配额、升级演练这些东西,越早准备,后面越轻松。