亚马逊AWS官方博客
为 Amazon EKS 节点调整预留系统资源
背景介绍
Kubernetes(k8s)是一个开源容器编排系统,用于自动化部署、扩展和管理容器化的工作负载。Amazon EKS 是一项托管的 k8s 服务,可以在亚马逊云中运行 k8s 应用程序。Amazon EKS是与 k8s 完全兼容的 ,经 CNCF 认证与 k8s 一致,因此运行于 k8s 的现有应用程序可与 Amazon EKS 兼容。
Amazon EKS 集群是由一组被称作节点(Node)的机器组成, 这些节点提供 k8s 运行环境,并负责运行容器化应用(这些应用被称为 Pod)。Node 上运行着 k8s 组件,包括 :
- kubelet – 与控制平面进行通信,负责执行控制平面在节点上的操作
- 容器运行时 – 提供节点上的容器运行时环境
- kube-proxy – 网络代理,负责集群内部和外部的网络通信。
本文目的
在这篇博文中,您将了解如何在 Amazon EKS 集群上对 Node 节点进行系统和 k8s 资源预留,并通过实验进行部署和验证。实验部分将在 Amazon Virtual Private Cloud (VPC) 中创建 Amazon EKS 集群,通过配置 kubelet 启动参数来实现预留系统资源,并进行验证。
Amazon EKS 节点的资源分配
Amazon EKS 集群中 Node 资源(内存、CPU 和存储),被分为多个部分,应用 Pod 会消耗一部分,但并非所有资源都可用于运行 Pod, 操作系统、kubelet 等也需要占用一部分资源。Node 需要分配以下资源:
- Pod 可用的资源
- 运行 k8s 组件所需的资源,例如 Kubelet、容器运行时、节点问题检测器等
- 运行操作系统和系统守护进程(如 SSH、systemd 等)所需的资源
- 保留到驱逐阈值的资源。
Node 的资源分配如下图所示:
基于 k8s 社区最佳实践,Node 上的资源分配需要根据实际使用场景,通过多种方式分配和限制资源,否则会出现资源争夺,导致 Node 资源短缺问题,最佳实践建议如下:
为什么要调整系统预留资源?
如果客户没有评估 Pod 可以使用的资源,或者在应用启动时并不清楚 Pod 会占用多少资源,从而没有对 Pod 规范定义 Limit 限制,进一步导致 Pod 会抢占 Node 上其他部分的资源。Pod 可以使系统资源饱和,使 Node 节点处于操作系统进程、 k8s 组件、 Pod 竞争系统资源的情况。如果 Pod 持续抢占 Node 资源,影响到系统进程的状态,kubelet 可能无法驱逐 Pod,并最终使节点状态变为 NotReady。
您可以通过为操作系统、k8s 组件预留系统资源来提高 Node 节点的稳定性。本文将通过调整操作系统、k8s 组件的系统预留资源,将 CPU 和内存等资源按照实际需要进行分配,在 Pod 抢占资源达到 Eviction threshold 时, Pod 将被驱逐出 Node。
Amazon EKS 节点默认预留资源是多少?
Amazon EKS 集群的节点有 EC2 和 Fargate 两种计算资源,对于 Fargate 计算资源的默认预留,每个 Pod 的内存预留增加 256 MB,并根据一定算法确定 Pod使用的CPU和内存资源,具体说明参考 Fargate Pod 配置 ,对于 EC2 计算资源,会按照一定算法设置CPU和内存预留值,预留值的大小和 EC2 实例类型有关,具体参考 文档 。
您可以通过 Amazon EKS 控制台,查看每个 Node 节点的资源分配情况,前提是您有查看 EKS 节点的权限,并且 EKS 控制台上展示出来的节点,查看方式: EKS 控制台上点击集群,点击节点选项,点击具体某个节点,进入详情查看节点的资源分配情况。
如果您的 EKS Node 节点 AMI 使用的是 EKS 优化的 Linux AMI ,EC2 在启动时会触发执行 bootstrap.sh 脚本,此脚本根据 EC2 实例上可用的 CPU 核心数和总内存计算 CPU 和内存预留。计算出的值将写入 Node 节点的 KubeletConfiguration 配置中,配置文件位于 /etc/kubernetes/kubelet/kubelet-config.json ,您可以通过SSH等方式连接到 Node 节点后查看该配置。
如何调整 EKS 节点的预留资源?
通过 kubelet 的配置进行系统资源预留,配置kube-reserved、system-reserved、eviction-threshold,kubelet 参数参考 文档 。Amazon EKS 集群提供四种计算资源提供方式,Amazon Fargate,托管节点组,非托管节点组,Karpenter 管理的节点,分别对每种方式进行说明:
Amazon Fargate
Amazon Fargate 不支持调整系统资源预留,因为具有以下要求并分配了固定大小的系统资源:
- 由于 Fargate 对每个节点只运行一个 pod,因此在资源较少的情况下不会出现驱逐 pods 的情况。所有 Fargate pods 都以担保优先级运行,因此请求的 CPU 和内存必须等于所有容器的限制。
- Fargate 会为所需 Kubernetes 组件(kubelet、kube-proxy 和 containerd)的每个 pod 的内存预留增加 256 MB,参考 Fargate Pod 配置 。
使用 Amazon Fargate,您无需再选择服务器类型、扩展节点组,Fargate将自动按需提供计算资源,以便运行Pods。您可以控制哪些 Pods要在 Fargate 上启动。详细信息参考 Amazon EKS Fargate 。
托管节点组
Amazon EKS Managed Nodegroup 调整系统资源的方式:
- 方案一:使用启动模板,在启动模板的高级信息部分使用 UserData。UserData 必须采用 MIME 分段归档格式(用于 Amazon Linux AMI)和 TOML 格式(用于 Bottlerocket AMI)。
- Amazon Linux AMI UserData 示例(注意:需要在启动模板中指定 AMI ID,本文的演练部分进行了展示):
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="==MYBOUNDARY=="
--==MYBOUNDARY==
Content-Type: text/x-shellscript; charset="us-ascii"
#!/bin/bash
set -ex
/etc/eks/bootstrap.sh {bootstrap.sh param} # 参数查看本文“bootstrap.sh 参数”部分
--==MYBOUNDARY==--
-
- Bottlerocket AMI UserData 示例(支持的其他配置参数查看文档 ):
[settings.kubernetes.kube-reserved]
cpu = "100m"
memory = "500Mi"
ephemeral-storage= "1Gi"
[settings.kubernetes.system-reserved]
cpu = "100m"
memory = "500Mi"
ephemeral-storage= "1Gi"
[settings.kubernetes.eviction-hard]
"memory.available" = "15%"
创建托管节点组,引用启动模板。Bottlerocket AMI 可以不用在启动模板中指定AMI ID,而在节点组上指定AMI类型。
- 方案二:不使用启动模板,通过eksctl命令。
- Amazon Linux AMI,通过 overrideBootstrapCommand 参数,在本文的演练部分进行了详细展示,示例 yaml 文件:
managedNodeGroups:
- name: x86-al2-on-demand-xl
# 建议使用 EKS优化的AMI,从 https://docs.thinkwithwp.com/zh_cn/eks/latest/userguide/retrieve-ami-id.html 中查询
ami: ami-06ce408c7d97c5f2d
instanceTypes: [ "m6i.xlarge", "m6a.xlarge" ]
minSize: 1
desiredCapacity: 2
maxSize: 3
volumeSize: 30
volumeType: gp3
volumeEncrypted: true
ssh:
allow: true
publicKeyName: us-east-1 # 替换ssh key
updateConfig:
maxUnavailablePercentage: 33
overrideBootstrapCommand: |
#!/bin/bash
/etc/eks/bootstrap.sh {bootstrap.sh param} # 参数查看本文“bootstrap.sh 参数”部分
-
- Bottlerocket AMI,示例 yaml 文件(支持的其他配置参数查看文档 ):
managedNodeGroups:
- name: x86-br-ami
amiFamily: Bottlerocket # 指定AMI类型
instanceTypes: [ "m6i.xlarge", "m6a.xlarge" ]
minSize: 1
desiredCapacity: 1
maxSize: 2
volumeSize: 30
volumeType: gp3
volumeEncrypted: true
updateConfig:
maxUnavailablePercentage: 33
bottlerocket: # 设置bottlerocket参数
settings:
kubernetes:
kube-reserved:
cpu: "100m"
memory: "500Mi"
ephemeral-storage: "1Gi"
system-reserved:
cpu: "100m"
memory: "500Mi"
ephemeral-storage: "1Gi"
eviction-hard:
"memory.available": "15%"
Amazon EKS 托管节点组会自动对节点(Amazon EC2 实例)进行预置和生命周期管理,您可以使用EKS 控制台管理托管节点组和 Amazon EC2 实例。详细信息参考 EKS 托管节点组 。
非托管节点组
Amazon EKS Self-Managed Nodegroup 调整系统资源的方式:
- 方案一:使用 eksctl 参数 kubeletExtraConfig 字段,自定义kubelet配置。实现参考 自定义kubelet配置,本文不进行实验展示。示例 yaml 文件:
nodeGroups:
- name: ng-1
instanceType: m6a.xlarge
desiredCapacity: 1
kubeletExtraConfig: # 设置预留资源
kubeReserved:
cpu: "100m"
memory: "500Mi"
ephemeral-storage: "1Gi"
kubeReservedCgroup: "/kube-reserved"
systemReserved:
cpu: "100m"
memory: "500Mi"
ephemeral-storage: "1Gi"
evictionHard:
memory.available: "200Mi"
nodefs.available: "10%"
featureGates:
RotateKubeletServerCertificate: true # has to be enabled, otherwise it will be disabled
- 方案二:使用 eksctl 参数。
- Amazon Linux AMI 示例 yaml 文件:
nodeGroups:
- name: ng-1
ami: ami-06ce408c7d97c5f2d # 建议使用 EKS优化的AMI,从 https://docs.thinkwithwp.com/zh_cn/eks/latest/userguide/retrieve-ami-id.html 中查询
instanceType: m6a.xlarge
desiredCapacity: 1
overrideBootstrapCommand: |
#!/bin/bash
/etc/eks/bootstrap.sh {bootstrap.sh param} # 参数查看本文“bootstrap.sh 参数”部分
-
- Bottlerocket AMI 示例 yaml 文件(支持的其他配置参数查看文档 ):
nodeGroups:
- name: ng-br
amiFamily: Bottlerocket # 指定AMI类型
instanceType: m6a.xlarge
desiredCapacity: 1
bottlerocket: # 设置bottlerocket参数
settings:
kubernetes:
kube-reserved:
cpu: "100m"
memory: "500Mi"
ephemeral-storage: "1Gi"
system-reserved:
cpu: "100m"
memory: "500Mi"
ephemeral-storage: "1Gi"
eviction-hard:
"memory.available": "15%"
Amazon EKS 非托管节点组,又称为自行管理的节点,您负责配置节点组,管理EC2生命周期,无法使用 Amazon EKS 控制台管理自行管理的节点组,需要使用其他工具比如eksctl,cloudformation,但您可以在EKS控制台上查看到自行管理的节点EC2实例。详细信息参考 EKS 自行管理的节点 。
Karpenter 管理的节点
Karpenter 调整系统资源,根据 Karpenter 版本不同对应不同的的方式。Karpenter 版本更新比较快,请参考最新的文档 Karpenter 。
在 v0.16 版本中引入 spec.kubeletConfiguration ,因此当版本大于等于 v0.16 时,比如 v0.16,v0.17 等,可以比较方便的使用 Provisioner 指定 spec.kubeletConfiguration ,如下示例 Provisioner 文件:
apiVersion: karpenter.sh/v1alpha5
kind: Provisioner
metadata:
name: default
spec:
labels:
intent: apps
requirements:
- key: karpenter.sh/capacity-type
operator: In
values: ["spot", "on-demand"]
ttlSecondsAfterEmpty: 60
ttlSecondsUntilExpired: 1296000
providerRef:
name: default
kubeletConfiguration: # 指定预留资源
systemReserved:
cpu: 500m
memory: 500Mi
ephemeral-storage: 1Gi
kubeReserved:
cpu: 500m
memory: 500Mi
ephemeral-storage: 3Gi
evictionHard:
memory.available: 5%
nodefs.available: 10%
nodefs.inodesFree: 10%
-
当版本小于 v0.16 时,比如 v0.15,v0.14 等,依然需要使用 NodeTemplate 的方式,如下根据 AMI 的示例。
- Amazon Linux AMI 配置自定义 AMI,示例 NodeTemplate 文件:
.
apiVersion: karpenter.k8s.aws/v1alpha1
kind: AWSNodeTemplate
metadata:
name: default
spec:
subnetSelector:
aws-ids: "subnet-077e3f24a51f979bf,subnet-0ddd33536fdafc3e9" # 设置Node子网
securityGroupSelector:
aws-ids: "sg-04748bd94e6195921,sg-02ca123d0b291cc96" # 设置Node安全组
amiFamily: Custom # 自定义AMI
amiSelector:
aws-ids: ami-0bd3c2e379f7f09c9 # 指定 AMI ID
userData: |
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="BOUNDARY"
--BOUNDARY
Content-Type: text/x-shellscript; charset="us-ascii"
#!/bin/bash
echo "Running my custom set-up"
/etc/eks/bootstrap.sh {bootstrap.sh param} # 参数查看本文“bootstrap.sh 参数”部分
--BOUNDARY--
- Bottlerocket AMI 配置自定义 UserData,示例 NodeTemplate 文件:
apiVersion: karpenter.k8s.aws/v1alpha1
kind: AWSNodeTemplate
metadata:
name: br
spec:
subnetSelector:
aws-ids: "subnet-077e3f24a51f979bf,subnet-0ddd33536fdafc3e9" # 设置Node子网
securityGroupSelector:
aws-ids: "sg-04748bd94e6195921,sg-02ca123d0b291cc96" # 设置Node安全组
amiFamily: Bottlerocket # 指定AMI类型
userData: |
[settings.kubernetes.kube-reserved]
cpu = "100m"
memory = "500Mi"
ephemeral-storage= "1Gi"
[settings.kubernetes.system-reserved]
cpu = "100m"
memory = "500Mi"
ephemeral-storage= "1Gi"
[settings.kubernetes.eviction-hard]
"memory.available" = "15%"
Karpenter 是开源、灵活、高性能的 Kubernetes cluster autoscaler。它通过快速启动适当大小的计算资源来响应不断变化的应用程序负载,让您通过快速、简单的计算配置使用亚马逊云提供的计算资源。
参数说明
bootstrap.sh 参数
/etc/eks/bootstrap.sh {test} --kubelet-extra-args '--system-reserved=cpu=200m,memory=200Mi,ephemeral-storage=1Gi --kube-reserved=cpu=200m,memory=200Mi,ephemeral-storage=1Gi --kube-reserved-cgroup=/kube-reserved --eviction-hard=memory.available<100Mi,nodefs.available<10% --feature-gates=RotateKubeletServerCertificate=true'
bootstrap.sh 是 EKS 优化的 AMI 中包含一个脚本,通常建议使用 EKS 优化的AMI,但您也可以构建完全自定义 AMI,构建时可以参考 bootstrap脚本 。
- {test} 代表集群名,替换为真实的集群名字
- system-reserved 指定系统预留资源,需要小于 Node 的 CPU/内存
- kube-reserved 指定 kube 预留资源,需要小于 Node 的 CPU/内存
- eviction-hard 指定 pod 驱逐阈值
- 使用ipv6 的集群,参数有些区别:
- 在bootstrap.sh 启动时增加以下参数,参考 文档:
--ip-family ipv6 --service-ipv6-cidr your-cidr
-
- 使用以下命令查看集群的
service-ipv6-cidr CIDR
范围:
- 使用以下命令查看集群的
aws eks describe-cluster --name my-cluster --query cluster.kubernetesNetworkConfig.serviceIpv6Cidr --output text
以下实验部分使用 bootstrap.sh 参数,以 {bootstrap.sh param} 占位符代替。
实验
先决条件
本文将使用 Amazon EKS 版本1.22 集群进行实验。
创建 EKS 集群
EKS 集群的创建和管理最常用的两种方式:Console 和 eksctl,通过 Console 可以界面操 作的方式一步步完成,而 eksctl 则是以命令行的方式进行操作。本文选择 eksctl 命令的方式进行集群创建,以下 eksctl config file 会在 us-east-1 区域创建一个 vpc,创建一个 EKS 1.22 版本的集群,添加 Add-on 组件。
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig
metadata:
name: test
version: "1.22"
region: us-east-1
vpc:
clusterEndpoints:
publicAccess: true
privateAccess: true
iam:
withOIDC: true
addons:
- name: vpc-cni
version: latest
- name: coredns
version: latest
- name: kube-proxy
version: latest
创建托管节点组
选项一:非启动模板,通过 eksctl 创建
以下为 Nodegroup 的 config file,修改 vpc.id,vpc.securityGroup,vpc.subnets。节点组参数 ami 从 Amazon EKS优化版AMI 选择各个区域 Amazon linux2 对应 EKS 版本的 ami id,本文选择 us-east-1 对应 1.22 版本的 AMI:ami-06ce408c7d97c5f2d 。选择 ssh key。
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig
metadata:
name: test
version: "1.22"
region: us-east-1
vpc:
id: vpc-0f7ba5ff584440bcd
securityGroup: sg-08ade32ee97bbfa71
subnets:
public:
public-1a:
id: subnet-068a1a5f81cb6426b
public-1b:
id: subnet-0b48430929e725d35
private:
private-nat1:
id: subnet-043ecf1e7dff2ee7f
private-nat2:
id: subnet-0de5bf410c47a776b
managedNodeGroups:
- name: x86-al2-on-demand-xl
ami: ami-06ce408c7d97c5f2d
instanceTypes: [ "m6i.xlarge", "m6a.xlarge" ]
minSize: 1
desiredCapacity: 2
maxSize: 3
volumeSize: 30
volumeType: gp3
volumeEncrypted: true
ssh:
allow: true
publicKeyName: us-east-1 # 替换ssh key
updateConfig:
maxUnavailablePercentage: 33
overrideBootstrapCommand: |
#!/bin/bash
/etc/eks/bootstrap.sh {bootstrap.sh param} # 参数查看本文“bootstrap.sh 参数”部分
labels:
os-distribution: amazon-linux-2
通过以下命令创建托管节点组,并启动自定义配置,请替换 {bootstrap.sh param} 为本文方案概述部分的 bootstrap.sh 参数。
eksctl create nodegroup --config-file=configfile.yaml
选项二:启动模板
- 指定AMI
从 Amazon EKS优化版AMI 选择 ami id,本文选择 us-east-1 对应 1.22 版本的 AMI:ami-06ce408c7d97c5f2d 。创建启动模板时,浏览其他 AMI,根据 AMI ID 找到对应 AMI。
- 安全组
安全组选择 Amazon EKS 集群安全组 ,如果自定义安全组,需要修改集群安全组和自定义安全组,确保可以相互访问。
ssh登陆密钥对(可选),根据需要选择。如果选择,需要在集群安全组中添加对应的ssh 22入站规则,允许访问。
- 存储
根据需要设置存储大小。
- 用户数据
高级详细信息,只需要提供用户数据,其他字段保持默认。请替换 {bootstrap.sh param} 为本文参数说明部分的 bootstrap.sh 参数。
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="==MYBOUNDARY=="
--==MYBOUNDARY==
Content-Type: text/x-shellscript; charset="us-ascii"
#!/bin/bash
set -ex
/etc/eks/bootstrap.sh {bootstrap.sh param} # 参数查看本文“bootstrap.sh 参数”部分
--==MYBOUNDARY==--
- 创建托管节点组
- 创建节点角色,参考 创建节点角色。
- 添加托管节点组,使用启动模板,选择已创建好的模板以及对应版本。
- 本文选择 EC2 实例类型 r5.large,建议选择支持 Amazon Nitro system 的实例类型。
- 选择子网,创建节点组,大约3分钟。
验证
- 节点组节点,点击节点名称,查看详情,确认节点的容量分配,已经按照启动模板的配置分配了保留资源:
- 如果节点组配置允许 ssh 登陆,或者 Node 支持 System Manager 登陆,连接到 Node 上,查看 kubelet 进程启动配置
ps -ef | grep kubelet | grep reserved
- 确认启动参数中增加了 system-reserved,kube-reserved,eviction-hard。
清理资源
为避免将来产生费用,您可以删除在此练习期间创建的所有资源。删除 Amazon EKS 节点组,删除集群,删除 VPC 等相关资源。
总结
Amazon EKS 集群的 Node 节点会默认预留系统资源,当有实际需求调整时,根据 Node 提供方式不同会有区别,分别从(Amazon Fargate,托管节点组,非托管节点组,Karpenter 管理的节点)这四种方式进行说明。同时以托管节点组的操作步骤为例,从创建集群到启动节点进行了实验演示。
参考资料
Reserve Compute Resources for System Daemons
Allocatable memory and CPU in Kubernetes Nodes
Customizing kubelet configuration
Bottlerocket kubernetes-settings