亚马逊AWS官方博客
使用托管节点组结合启动模板简化EKS升级与运维
背景
随着应用容器化不断流行与深入,采用Kubernetes(K8S)作为容器编排方式的应用也随之增加。作为亚马逊云科技的用户,在云上使用Amazon Web Service托管的K8S服务Elastic Kubernetes Service(EKS)服务的客户也在不断增加。同时根据K8S社区的发布规则K8S每年会有三个小版本的发布, 相应的EKS也会跟随上游K8S的版本发布3个版本,目前支持的版本以及相应终止支持的时间信息可以参考亚马逊EKS发布日历。每个上游的K8S版本都会有1年支持窗口加上2个月升级过渡窗口,为了保持与K8S社区版本的同步来获得社区的支持,客户每隔一段时间都要对现有的K8S或者EKS集群做升级,也就是说K8S与EKS的升级已经成为常态。
在EKS上这个升级主要涉及到控制平面升级、数据平面升级与插件组件升级。其中以数据平面的升级最为繁琐与复杂,所以本着减少无差别的繁重的运维工作为出发点,本文通过一个端到端的实验详细介绍通过使用托管节点组与启动模板简化客户的升级操作的过程与方法,从而为运维人员减负。并实现应用的平滑升级与灵活回退,进而保证应用与业务的稳定和可用性。
了解EKS的用户或者读者知道EKS的数据平面可以分为有服务器与无服务器两大类:有服务器即采用EC2的方式来运行K8S的应用;无服务器的方式即EKS Fargete,客户将工作负载部署在Fargate的环境而不是自己的VPC内,EKS节点对客户透明。有服务器EC2的节点管理方式又分为两种模式:托管节点组(Managed Node Group)与非托管节点组(Unmanaged Node Group)。非托管节点组的命名来自于官方的EKS创建与维护工具eksctl.io文档中Unmanaged Node Group的中文直译,根据EKS官方文档, 非托管节点组模式被称为自行管理的节点(Self-managed nodes),为了简化与对比起见本文统一称为非托管节点组。托管节点组即将集群EC2节点的创建与生命周期管理由EKS服务来自动化管理,不需要客户干预。根据eksctl对非托管节点组的设计原则,非托管节点组中的节点除了可以做扩缩容其他配置均不可变。下表列出了两种模式的主要区别:
项目 | 托管节点组 | 非托管节点组 |
升级方法 | 原地升级 | 需要创建新的非托管节点组 (注:适用于通过eksctl创建的非托管节点组,其他方式可以通过更新CloudFormation 栈的方法更新AMI来升级,细节可以参考:官方文档) |
EKS管理控制台可见 | 是 | 否 |
配置变更支持 | 是(可以变更的选项有:Min Size,Max Size,Desired Size, Kubernetes labels, taints, tags, Maximum unavailable等配置,其他配置项变更需要通过切换启动模板版本实现。) |
否(除节点数量扩缩) |
支持关联启动模板 | 是 | 否 |
自定义AMI支持 | 是 | 是 |
考虑到EKS 1.17将于2021年11月终止支持,同时目前绝大部分客户使用的还是EC2的方式,并且因为非托管节点组相对于托管节点组的功能是更早发布,加上早期的托管节点组功能较少的原因,一部分客户还停留在非托管节点组的模式,所以本文的实验通过将非托管节点组转换为托管节点组结合启动模板的方式简化EKS 集群从1.17升级到1.18的升级过程(虽然本实验进行的的是1.17到1.18的升级,但过程和其中的方法对其他EKS版本的升级同样适用,比如从1.15升级到1.16或者1.18升级到1.19)。其中主要内容涵盖:控制平面升级、数据平面非托管节点组迁移到托管节点组、托管节点组升级与变更等。
在升级路径的选择上,也可以先将控制平面由1.17升级到1.18,然后创建托管节点组,再将工作负载由非托管节点组迁移到托管节点组,这样可以减少一次数据平面托管节点组的升级,加快集群整体升级的过程。因为本文的重点是介绍推广托管节点组结合启动模板的实践方法,先升级控制平面再创建托管节点组的做法,笔者测试下来对于文中简单的样例应用Nginx是可以的,遵从K8S官方的Version Skew Policy:kubelet must not be newer than kube-apiserver, and may be up to two minor versions older。笔者尝试过将控制平面从1.17升级到1.18再连续升级到1.19,然后再创建托管节点组与迁移工作负载也是可以成功通过的,但如果集群中有对1.17到1.18/1.19版本变化敏感或者需要改造的应用,稳妥起见还是建议按照本实验的顺序来操作,并引入必要的测试。有兴趣的读者可以自行测试验证。
本文目标读者:EKS运维与管理人员
实验所需时长:3小时
操作步骤
前提:当前使用的用户具有Administrator Access权限
准备Cloud9实验环境
在AWS管理控制台中选择Cloud9服务,然后创建一个名称为:eksworkshop的环境,将Cost-saving setting选项设置为:After four hours,其他配置保持默认。创建完毕后关闭Welcome和底部的工作区页面,然后在主工作区中打开一个新的Terminal。
在IAM服务中,使用链接创建一个名称为:eksworkshop-admin的角色,确认:AWS service和EC2被选中,点击下一步,确认AdministratorAccess策略被选中,点击下一步,跳过Tag选项,点击下一步在Review页面中输入eksworkshop-admin作为新角色的名称,点击创建角色完成创建。
点击链接在EC2服务中查看刚刚创建的Cloud9环境对应的EC2实例,选中该实例,然后在菜单选择:Actions / Security / Modify IAM Role,在IAM Role的下拉列表中选择eksworkshop-admin的角色,点击保存。
返回刚刚创建好的Cloud9环境,点击页面右上角的齿轮,打开首选项设置页面,然后选择AWS SETTINGS,关闭AWS managed temporary credentials单选框,最后关闭首选项设置页面。
在打开的终端中运行以下命令确认临时的秘钥凭证已经被删除干净,并验证在返回结果的ARN 中包含eksworkshop-admin。
rm -vf ${HOME}/.aws/credentials
aws sts get-caller-identity
运行下列脚本安装实验所需的Kubernetes 工具:eksctl,kubectl,helm,jq,aws cli
创建EKS集群(版本:1.17)
配置创建集群需要的环境变量
运行下列命令创建EKS集群配置模板文件 eks-cluster.yml.template
利用 eksctl 工具来创建 EKS 集群,运行下列命令创建一个 EKS 1.17 的集群,同时会创建一个新的 VPC,并且在该VPC中创建 一个含有2个节点的非托管节点组,整个过程大概需要 20 分钟左右(集群和节点组各10分钟左右)。
envsubst < ~/environment/scripts/eks-cluster.yml.template > ~/environment/scripts/eks-cluster.yml
eksctl create cluster -f ~/environment/scripts/eks-cluster.yml
在EKS服务界面上验证集群eksworkshop已经成功创建。运行下列命令验证 EKS 集群节点组ung-1已经成功创建
eksctl get nodegroup --cluster $EKS_CLUSTER_NAME
运行下列命令测试 EKS 集群节点是否正常工作
kubectl get nodes --show-labels
运行下列命令将当前登录管理控制台的用户加入到EKS集群管理员组中,这样使得当前登录用户可以在EKS服务界面上查看集群信息。
我们可以在EC2控制台看到新创建了2个名称为eksworkshop-ung-1-Node类型为m5.large 的EC2实例,也可以在管理控制台的EKS服务的集群列表中查看刚刚创建好的集群节点、网络和其他集群配置信息。因为我们刚刚创建的是一个非托管节点组,所以如下图所示在EKS > Clusters > eksworkshop > Compute界面查看托管节点组为空
同时,因为eksctl工具的底层实现是依赖CloudFormation服务的,所以可以再CloudFormation服务的管理界面查看为了创建集群而新建的2个CloudFormaiton模板:集群控制平面CloudFormaiton Stack、非托管节点组CloudFormaiton Stack。+
部署样例工作负载
通过下列命令参考样例Nginx程序已经成功部署
kubectl get deploy
kubectl get po
创建启动模板
进入EC2服务,选择Launch Templates > Create launch template,分别填入
- Launch template name:demo
- Instance type:large
- Security groups:在EKS集群管理控制台 EKS > Clusters > eksworkshop > Networking中显示Cluster security groupInfo的安全组ID
- Resource Tags Key: Name, Value: eksworkshop-mng-1, Resource types: Instances
- User Data: 如果某些客户在使用非托管节点组的配置YAML文件中有使用:preBootstrapCommands或者overrideBootstrapCommand之类的一些自定义命令,那么在转换到托管节点组加启动模板这种方式后将不再支持,如果继续使用会出现错误:cannot set instanceType, ami, …, preBootstrapCommands, overrideBootstrapCommand, placement in managedNodeGroup when a launch template is supplied。用户可以将这些选项中配置的SHELL命令迁移到User Data中。如果有使用自定义AMI,则必须在User Data中填入下列命令将节点加入到集群,否则会出现错误:node bootstrapping script (UserData) must be set when using a custom AMI。具体User Data输入的MIME格式要求请参考这里的官方文档。
#!/bin/bash
/etc/eks/bootstrap.sh cluster_name
点击Create launch template创建启动模板。在启动模板的版本列表中查看刚刚创建好的版本号为1的启动模板,因为启动模板的版本是不可变的,只能通过选择版本1后点击Actions > Modify template (Create new version)来创建新的版本。
如果在EKS集群中有使用自定义AMI,那么可以在创建模板过程中指定已经定义好的AMI。需要注意的是,根据EKS升级官方文档在升级控制平面之前要求自定义AMI的节点版本需要与控制平面的版本相同。在本实验中来说就是要求自定义AMI及kubelet版本必须是1.17。这样当控制平面升级到1.18以后就会导致托管节点组还停留在1.17版本,存在一个小版本的差异。这样在托管节点组升级到1.18之前是不能将控制平面升级到版本1.19,否则得到错误提示:Nodegroups xxx must be updated to match cluster version 1.1x before updating cluster version。如果自定义AMI的节点组比控制平面低一个版本,则不能直接在界面上通过点击“Update now”操作来升级节点组。如果后续有继续将版本从1.18升级到更高版本的需求则需要根据目标EKS版本的EKS优化AMI重新定义自己的AMI,通过创建新的启动模板版本来指定这个新的AMI,然后再托管节点组中切换启动模板版本即可完成升级。具体切换启动模板版本的流程可以参考下面的章节“切换启动模板版本”。
创建托管节点组
运行下列命令设置环境变量并创建托管节点组
整个过程大概需要 3分钟左右。之后我们可以在EC2控制台看到新创建了2个名称为eksworkshop-mng-1类型为m5.large 的EC2实例。同时因为我们刚刚创建的是一个托管节点组,所以如下图所示在EKS > Clusters > eksworkshop > Compute界面可以查看到刚刚创建的托管节点组。
注意上述托管节点组的配置文件中设置的版本1.17是与控制平面一致的版本,如果托管节点组配置的版本与EKS集群控制平面不一致时,eksctl会自动使用控制平面版本。
从选中的部分可以看出托管节点组mng-1对应的启动模本名称为demo版本为1。也可以运行下列命令验证 EKS 集群节点组mng-1已经成功创建
eksctl get nodegroup --cluster $EKS_CLUSTER_NAME
运行下列命令测试 EKS 集群新加入的两个节点是否正常工作
kubectl get nodes --show-labels
需要特别指出的是启动模板对于创建托管节点组是一个推荐的可选项。不预先配置启动模板,而是直接利用下列的配置文件也可以创建托管节点组。因为在配置中没有显示的配置启动模板,eksctl会根据配置自动生成一个名称为“eksctl-集群名称-nodegroup-托管节点组名称 (N)”的一个启动模板,这个启动模板是由eksctl创建、管理和维护,因此不建议手动创建新版本修改或者复用。这种方式下创建的每个托管节点组的启动模板都是独立的,不能复用,如果有统一配置的需求和后续针对单个节点组的修改需求就更加麻烦。同时因为没有在配置文件中显示的指定启动模板,需要根据命名规则或者在EKS的托管节点组控制界面上查询这个启动模板,所以这种方式不利于配置变更的跟踪。而显示的声明在多个托管节点组中可以共用一个启动模板,当出现不同的配置需求时又可以通过新建启动模板版本来解决,相对于隐式的方式更加灵活高效,所以在本实验中托管节点组采用的是显示的启动模板。
迁移工作负载并删除非托管节点组
因为非托管节点组的不可变性,除了改变节点数量无法更改其配置,所以当遇到升级集群版本的情况是需要创建新的非托管节点组然后迁移负载再删除旧的节点组的方法来实现升级节点组。而托管节点组可以做到原地(In place)升级,所以本实验先将样例工作负载迁移到托管节点组再做集群的升级。
运行下列命令查验当前的nginx pod运行在非托管节点组的节点上
kubectl get po -o wide
运行下列命令将在非托管节点组的节点上的工作负载驱逐到刚刚创建的托管节点组
eksctl drain nodegroup --cluster=$EKS_CLUSTER_NAME --name=$EKS_UNMANAGED_NODEGROUP_NAME
运行命令:kubectl get no
可以发现旧节点组的状态已经变为:Ready,SchedulingDisabled,重新运行下列命令查验当前的nginx pod运行在托管节点组的节点上
kubectl get po -o wide
运行下列命令删除非托管节点组
eksctl delete nodegroup --cluster $EKS_CLUSTER_NAME --name $EKS_UNMANAGED_NODEGROUP_NAME
运行下列命令监视节点删除情况直至非托管节点组被完全删除。
kubectl get no -w
升级集群控制平面
如下图所示在EKS集群控制台上点击Update Now升级集群控制平面,因为K8S需要逐个版本升级,所以只有1.18目标版本是可选状态。
升级后如下图所示集群处于Updating状态,整个升级过程大约需要30~40分钟。这步升级操作也可以通过eksctl命令或者aws cli来完成,具体做法请参考官方文档。
需要注意的是如果有多个托管节点组,在升级控制平面之前,需要确认所有的托管节点组都已经升级到控制平面的版本,否则升级时会得到错误提示:Nodegroups xxx must be updated to match cluster version 1.1x before updating cluster version。
升级托管节点组
如下图所示进入EKS > Clusters > eksworkshop > Compute可以看到New AMI Release versios are avaiable for 1 Node Group的提示,并且在Node Groups中mng-1的AMI release version列的旁边出现了Update now的链接
在上面的弹出的对话框中可以看到Update Strategy设置为Rolling update,也即滚动更新,点击Update开始节点组升级更新,整个过程需要约20分钟。
其间可以在EC2控制台中查看新旧节点的变化情况,在新启动的实例细节信息里查验AMI name已经改为amazon-eks-node-1.18-v2021xxxx。通过运行命令:kubectl get no可以看到旧的节点被设置为SchedulingDisabled状态,Nginx Pod在被逐步迁移到新的节点上。
可以通过如下图所示的编辑托管节点组 EKS > Clusters > eksworkshop > Node Group: mng-1 > Edit Node Group的Node Group update configuration来设置最大不可用节点数目或者比例数,从而控制滚动更新的颗粒度。当然也可以变更最小、最大、期望节点数,k8s labels,taints和tags等其他配置。
相比之下,如果我们这里使用的依旧是非托管节点组,那么在这个步骤中我们就要重新创建一个与控制平面版本一致的1.18的新的非托管节点组,然后将工作负载从旧的1.17的节点组迁移到新的1.18的节点组再删除1.17的非托管节点组,这个过程相对于上述托管节点组的一键升级的流程复杂许多,而且存在一个新旧非托管节点组同时存在的时间窗口给集群的管理与维护增加了难度与不确定性。更令运维人员头疼的是,这个升级过程在后续的版本升级中(比如1.18到1.19)仍然需要重复一遍。对比可见数据平面的升级在托管节点组的支持下变得非常简单方便。
切换启动模板版本
在日常集群的维护中,我们经常会有一些变更需求,比如切换实例类型,修改各种资源比如EC2的名称等,这些在非托管节点组是无法实现的,而在配置了启动模板的托管节点组中可以轻松实现,下面将演示将节点组实例类型切换为m5.2xlarge的方法。
基于启动模板demo的版本1新创建一个新版本:version 2,将实例类型设置为m5.2xlarge同时保持其他选项不变。
等待托管节点组mng-1升级到1.18完成后,可以看到节点组的AMI release version改为1.18.20-xxxx,同时因为我们增加了一个新的启动模板的版本,点击Change version将mng-1节点组切换到新创建的版本2从而修改节点组的实例类型到m5.2xlarge。
点击Update开始节点组滚动更新,类似的整个过程需要约20分钟。等待更新完毕后,按照相同的方法可以重新切换回版本1。启动模版的版本信息可以通过下列命令导出到yaml作为配置变更管理的一部分通过git等源代码版本管理工具来管理。
aws ec2 describe-launch-template-versions --launch-template-name demo --output yaml
需要注意的是在启动模板的不同版本间做切换目前不支持在有使用EKS优化AMI与自定义AMI的不同版本间切换。否则会得到错误:You cannot specify an image id within the launch template, since your nodegroup is configured to use an EKS optimized AMI(默认EKS优化版本改为自定义AMI版本)或者The request must contain the parameter ImageId(自定义AMI版本改为默认EKS优化版本)。如果有将默认AMI替换为自定义AMI的需求,可以通过创建一个新的托管节点组来引用配置有自定义AMI的启动模板的版本来解决。
删除EKS集群和Cloud9环境
eksctl delete cluster --name $EKS_CLUSTER_NAME
最后,在AWS控制台的Cloud9服务的环境列表中删除eksworkshop。
总结
本文通过一个端到端的例子说明了将非托管节点组转换到托管节点组来实现工作负载的迁移。同时完成集群和托管节点组从 1.17到1.18的版本升级。最后通过在不同启动模板版本间切换的方法实现了节点组配置的灵活原地更新。需要特别指出虽然上述实验进行的的是1.17到1.18的升级,但过程对于其他EKS版本的升级同样适用,比如从1.18升级到1.19。
相比EKS集群的托管节点组,非托管节点组具有不可变性,所以必须通过新建节点组然后迁移工作负载的方式来更新。而托管节点组则可以通过改变启动模板的版本然后进行滚动更新来实现常用配置的变更,同时在出现失败的情况下支持回退,所以在日常变更管理与集群版本升级上更加简便。并且一个集群或者多个集群中的多个节点组可以共用一个启动模板,极大的简化了维护与管理的成本。另外将多项节点组配置选项转移到启动模板中,实现了节点组配置一定程度的解耦。最后因为启动模板支持多个版本,同一托管节点组可以在同一个启动模板的不同版本间灵活切换,也极大的方便了日常节点组的变更与维护。关于更多托管节点组的新功能请参考托管节点组最新动态博客与EKS官方文档。
最后需要指出的是EKS升级的范畴远大于文中介绍的内容,鉴于篇幅所限,其他方面的升级方法请读者自行参考官方K8S升级手册与官方EKS升级文档。
参考资料
- https://docs.aws.amazon.com/eks/latest/userguide/kubernetes-versions.html
- https://eksctl.io/usage/managing-nodegroups/#nodegroup-immutability
- https://docs.aws.amazon.com/eks/latest/userguide/eks-compute.html
- https://docs.aws.amazon.com/eks/latest/userguide/update-cluster.html
- https://aws.amazon.com/blogs/containers/catching-up-with-managed-node-groups-in-amazon-eks/
- https://aws.amazon.com/blogs/containers/introducing-launch-template-and-custom-ami-support-in-amazon-eks-managed-node-groups/
- https://docs.amazonaws.cn/eks/latest/userguide/launch-templates.html
- https://www.eksworkshop.com/
- https://eksctl.io/usage/schema/