2.8 升级
集群生命周期管理与集群部署密切相关。虽然集群部署系统不一定需要考虑到未来的升级,但是当有足够多的问题堆积时建议你考虑一下。至少,你的升级策略需要在投入生产前解决。只能部署平台却没有升级和维护的能力是相当危险的。当你看到生产环境中的工作负载运行在远远落后于最新版本的Kubernetes上时,说明该Kubernetes平台没有升级能力。在我们第一次运行生产的工作负载时,将会有相当多的工程预算被用于处理发现缺失的功能或一些会造成损失的问题。随着时间的推移,这些功能将被添加,问题将会被解决,到时,被遗忘的升级策略将会变得至关重要。尽早为升级制定计划,你将会在未来感谢现在的做法。
在讨论这个主题时,我们将首先研究平台的版本控制,以确保对平台本身与将使用它的工作负载之间的依赖关系有很好的理解。我们还将讨论如何在出错的情况下计划回滚,以及如何进行测试来验证一切是否按计划进行。最后,我们将比较各种升级Kubernetes的具体策略。
2.8.1 平台的版本控制
首先,对你的平台进行版本控制,并记录该平台中使用的所有软件的版本。这涉及机器的操作系统版本和安装在机器上的所有软件包,如容器运行时。它显然包括正在使用的Kubernetes版本。而且它还应该包括为构成你的应用平台而添加的每个附加组件的版本。对于团队来说,采用Kubernetes版本作为应用平台版本是常见的,这样大家就知道1.18版本的应用平台使用Kubernetes 1.18,而不需要花费多余的时间去查找。与仅仅进行版本控制和记录相比,这是微不足道的。你可以使用团队喜欢的任何系统的版本作为应用平台的版本,但是你要保证有这个系统并且持续不断地记录这个系统并认真地使用它。我唯一反对的是把你的平台版本随意与该系统的多个组件进行绑定,它可能偶尔会引发混乱。例如,你由于安全漏洞而需要更新你的容器运行时版本,并且你需要在应用平台版本中反映这一点。如果使用语义上的版本惯例,这看起来像是对bugfix版本号的更改。可能会与Kubernetes本身的版本变化相混淆,即v1.18.5→1.18.6。我们希望你给应用平台提供自己独立的版本号,特别是在使用遵循major/minor/bugfix惯例的语义上的版本管理时。软件有自己的独立版本,并依赖于其他软件及其版本,这几乎是普遍现象。如果你的平台也遵循这些惯例,那么所有的工程师都会立即明白其中的含义。
2.8.2 失败计划
从升级过程中会出现错误的前提开始。想象一下你自己处在一个必须从一个灾难性的失败中恢复的情况,并将这种恐惧和痛苦作为动力,为可能出现的这种结果做好充分准备。建立自动化,为你的Kubernetes资源进行备份和恢复——这其中既包括直接的etcd快照,也包括通过API得到的Velero备份。对你的应用程序使用的持久性数据也要这样做。还要直接解决你的关键应用程序及其依赖项的灾难恢复问题。对于复杂的、有状态的、分布式的应用程序,仅仅恢复应用程序的状态和Kubernetes资源而不考虑顺序和依赖关系可能是不够的。我们需要对所有可能的故障模式进行思考,并开发自动恢复系统来修复,然后对其进行测试。对于最关键的工作负载及其依赖组件,需要准备好备用集群,以备故障时使用,然后在可能的情况下自动测试故障情况并进行故障切换。
仔细考虑你的回滚路径。如果一个升级引起了你无法立即诊断的错误或中断,拥有回滚选项是很好的保险。复杂的分布式系统可能需要时间来排除故障,而生产系统中断带来的压力和干扰可能会延长排除故障的时间。在处理复杂的Kubernetes平台时,预先确定的操作手册和自动化回退比以往任何时候都重要。但说实话,在现实世界中回滚并不总是一个好的选择。例如,如果你的平台已经升级了很多个版本,回滚所有早期的变化可能是一个糟糕的主意。你需要提前考虑清楚哪些地方是不可挽回的,并在你执行这些操作之前制定策略。
2.8.3 集成测试
拥有一个包含所有组件版本的文档化版本控制系统是一回事,但如何管理这些版本是另一回事。在像基于Kubernetes平台这样复杂的系统中,确保每一次都能按照预期的方式进行整合和工作是一个相当大的挑战。不仅平台的所有组件之间的兼容性至关重要,而且在平台上运行的工作负载与平台本身的兼容性也必须进行测试和确认。对你的应用程序来说,要倾向于平台无关,以减少可能出现的平台兼容性问题,在许多情况下,应用程序的工作负载在充分利用平台功能时将会产生巨大的价值。
虽然所有平台组件的单元测试以及所有其他健全的软件工程实践都很重要。但是,集成测试也同样重要,尽管它的挑战性更大。Sonobuoy一致性测试工具是协助这项工作的一个优秀工具。它最常用于运行上游的Kubernetes端到端测试,以确保你有一个正确运行的集群,即所有集群的组件都按预期工作。通常情况下,团队会在提供新集群后运行Sonobuoy扫描,用以自动化检查通常需要手动检查的控制平面Pod和部署测试工作负载的过程,以确保集群正常运行。然而,我建议你可以再优化一下,开发你自己的插件测试平台的具体功能和特点以及对你的组织至关重要的操作,并定期运行这些扫描。使用Kubernetes CronJob来运行至少一个插件子集。虽然现在完成这些功能并不完全是开箱即用的,但它们通过一点工程设计便可以解决,并且非常值得尝试。将扫描结果作为指标公开,可以在仪表盘上显示并发出警报。这些一致性扫描基本上可以测试一个分布式系统的各个部分是否在一起工作,以产生你所期望的功能和特性,并构成一个非常强有力的自动化集成测试方法。
同样,集成测试必须扩展到在平台上运行的应用程序。不同的应用开发团队将采用不同的集成测试策略,这可能在很大程度上不在平台团队的掌控之中,但要大力提倡。在一个与生产环境密切相关的集群上运行集成测试对于利用平台特性的工作负载来说更加重要。Kubernete operator便是这方面的一个引人注目的例子。它们扩展了Kubernetes的API,并自然地与平台深度集成。如果你使用operator来部署和管理组织的任何软件系统的生命周期,那么你必须执行跨平台版本的集成测试,特别是当涉及Kubernetes版本升级时。
2.8.4 策略
我们要看一下升级基于Kubernetes的平台的三种策略:
•集群替换
•节点替换
•就地升级
我们将按照从成本最高、风险最低到成本最低、风险最高的顺序来讨论这些策略。与大多数事情一样,存在一种权衡,消除了极端、普遍理想的解决方案的可能。需要考虑成本和效益,以找到适合你的需求、预算和风险容忍度的解决方案。此外,在每个策略中,都有不同的自动化和测试的程度,这也取决于工程预算、风险容忍度和升级频率等因素。
请记住,这些策略并不是相互排斥的。你可以组合使用它们。例如,你可以对一个专门的etcd集群进行就地升级,然后对Kubernetes集群的其他部分使用节点替换。你也可以在风险容忍度不同的层级使用不同的策略。然而,建议在所有环境使用相同的策略,这样你在生产中使用的方法将在开发和生产镜像环境中得到彻底的测试。
无论你采用哪种策略,有几个原则是不变的:完备的测试和尽可能高的自动化程度。如果你建立自动化程序来执行操作,并在测试、开发和生产镜像集群中彻底测试自动化,你的生产升级将更不可能给最终用户带来问题,也更不可能在你的平台运营团队中造成压力。
集群替换
集群替换是成本最高、风险最低的解决方案。它是低风险的,因为它遵循适用于整个集群的不可改变的基础设施原则。升级是通过在旧的集群旁边部署一个全新的集群来进行的。工作负载从旧集群迁移到新集群。随着工作负载的迁移,升级后的新集群会根据需要进行扩容,旧集群的工作节点会减少。但是,在整个升级过程中,会增加一个完全不同的新集群以及与之相关的成本。扩大新集群和缩小旧集群减轻了这种成本,也就是说,如果你要升级一个300个节点的生产集群,你不需要在一开始就配置一个有300个节点的新集群。你可以配置一个有20个节点的集群。当最初的几个工作负载被迁移后,你可以缩小使用量减少的旧集群的规模,并扩大新集群的规模以适应进入的其他工作负载。
使用集群的自动缩放和超额配置可以使这一点变得非常容易,但是仅仅是升级还不足以成为使用这些技术的合理理由。在解决集群替换问题时,有两个共性的问题。
一个问题是管理入口流量。当工作负载从一个集群迁移到下一个集群时,流量需要被重新分配到新的、升级的集群。这意味着公开暴露的工作负载的DNS不会解析到集群入口,而是解析到全局服务负载平衡器(GSLB)或反向代理,反过来将流量路由到集群入口。这为你提供了一个管理进入多个集群的流量路由的点。
另一个问题是持久的存储可用性。如果使用一个存储服务或设备,同样的存储需要从两个集群中访问。如果使用托管的服务,如来自公共云服务商的数据库服务,你必须确保同一服务在两个集群中都是可用的。在私人数据中心,这可能是一个网络和防火墙的问题。在公有云中,这将是一个网络和可用性区域的问题,例如AWS EBS卷可以从特定的可用性区域使用,而AWS的管理服务通常有特定的虚拟私有云(VPC)关联。出于这个原因,你可以考虑为多个集群使用一个VPC。通常情况下,Kubernetes安装程序假定每个集群有一个VPC,但这并不总是最佳模式。
接下来,你将关注工作负载的迁移。主要是指Kubernetes资源本身——Deployment、Service、ConfigMap等。你可以通过以下两种方式之一进行工作负载迁移:
1.从已声明的真实来源重新部署。
2.将现有资源从旧集群中复制过来。
第一种方法可能涉及将你的部署管道指向新集群,并让它将相同的资源重新部署到新集群中。这假设你在版本控制中的资源定义的真实来源是可靠的,并且没有发生过就地更改。在现实中,这是很不常见的。通常情况下,管理员、控制器和其他系统已经进行了就地更改和调整。如果是这种情况,你将需要选择方案2,复制现有资源并将其部署到新的集群中。这时,像Velero这样的工具就会变得非常有价值。Velero通常被吹捧为一种备份工具,但它作为迁移工具的价值同样高,甚至可能更高。Velero可以拍摄集群中所有资源的快照,或者一个子集。因此,如果你一次迁移一个命名空间的工作负载,你可以在迁移时对每个命名空间进行快照,并以高度可靠的方式将这些快照恢复到新集群中。它不是直接从etcd数据存储中获取这些快照,而是通过Kubernetes API,所以只要你能为两个集群的API服务器提供对Velero的访问,这种方法就会非常有用。图2-6说明了这种方法。
图2-6:通过使用Velero的备份和恢复在集群之间迁移工作负载
节点替换
节点替换选项代表了成本和风险的中间地带。它是一种常见的方法,并得到了Cluster API的支持。如果你正在管理较大的集群,并且对兼容性问题有充分的了解,那么这便是一个可以接受的选择。就你的集群服务和工作负载而言,你是在就地升级控制平面。如果你就地升级Kubernetes,而其中的某个工作负载由于所使用的API版本不存在导致了兼容性问题,那么你的工作负载可能会遭受中断。因此,兼容性问题是节点替换选项的最大风险之一,有几种方法可以缓解这种情况:
•阅读Kubernetes发布说明。在推出包括Kubernetes版本升级的新版平台之前,请仔细阅读CHANGELOG。在CHANGELOG里,任何API的废弃或删除都有详细的记录,因此你可以通过该文档获得大量信息并提前做好准备。
•在生产环境前进彻底测试。在推广到生产之前,在开发和生产镜像集群中广泛地运行新版本的平台。在最新版本的Kubernetes发布后不久后就在开发环境中运行,你将能够进行彻底的测试,并在生产环境中运行最新版本的Kubernetes。
•避免与API紧密耦合。虽然这并不适用于在集群中运行的平台服务。因为那些服务需要与Kubernetes紧密集成。但要尽可能保持你的终端用户、生产环境中的工作负载与平台无关。不要把Kubernetes的API作为一个依赖项。例如,你的应用程序应该对Kubernetes的内容一无所知。它应该简单地接受一个环境变量或读取一个暴露给它的文件。这样一来,无论API如何变化,只要用于部署应用程序的清单被升级,应用程序的工作负载本身将继续顺利地运行。如果你发现你想在工作负载中利用Kubernetes功能,可以考虑使用Kubernetes operator。operator中断不会影响你的应用程序的可用性。虽然operator的中断将是一个急需解决的问题,但是它不会是你的客户或最终用户看到的问题,这和直接依赖Kubernetes API有天壤之别。
当你提前建立经过良好测试和验证的机器镜像时,节点替换选项可能非常有益。然后你就可以把新的机器调出来,并随时把它们加入集群中。这个过程将是快速的,因为所有更新的软件(包括操作系统和软件包)都已经安装好了,部署这些新机器可以使用与原来的部署相同的过程。
如果你正在运行一个专门的etcd集群,并且需要更换节点时,你将需要从控制平面开始。集群的持久化数据是至关重要的,必须小心对待。如果你在升级你的第一个etcd节点时就遇到了问题,并且你准备得当,中止升级将是相对简单的解决方案。如果你升级了所有的工作节点和Kubernetes控制平面,然后发现自己在升级etcd时遇到了问题,那么回滚整个升级将是不现实的,你已经失去了中止整个过程、重新组织、重新测试并在以后恢复的机会。你需要根据现场情况解决出现的问题,或者至少要努力确保能在一段时间内安全地保持现有版本的原样。
对于一个专用的etcd集群,考虑以减法方式更换节点。也就是说,移除一个节点,然后加入升级后的替代节点,而不是先在集群中加入一个节点,然后移除旧的。这种方法使你有机会让每个etcd节点的成员列表保持不变。例如,在一个有三个节点的etcd集群中添加第四个成员,将需要更新所有etcd节点的成员列表,这将需要重新启动。如果可能的话,放弃一个成员,用一个与旧成员具有相同IP地址的新成员取代它,这样做的破坏性会小得多。etcd的升级文档非常详细,可以让你考虑对etcd进行就地升级。这将需要对机器上的操作系统和软件包进行适当的就地升级,但这通常是很容易接受的,而且非常安全。
对于控制平面的节点,它们可以被叠加替换。在安装了升级后的Kubernetes二进制文件kubeadm、kubectl和kubelet的新机器上使用带有--control-plane标志的kubeadm join。当每个控制平面节点上线并确认运行时,一个旧版本的节点将被清空、删除。如果你正在运行位于控制平面节点上的etcd,则在确认可操作性时包含etcd检查,并在需要时包含etcdctl来管理集群成员。
然后你可以继续替换工作节点。这些可以以加法或减法的方式进行,一次一个或几个。你所面临的问题将是集群的利用率。如果你的集群利用率很高,你会希望在清空和移除现有节点之前增加新的工作节点,以确保你有足够的计算资源用于被替换的Pod。同样,一个好的模式是使用安装了所有更新的软件的机器镜像,这些镜像被带到网上并使用kubeadm join加入集群中。而且,这也可以使用许多与集群部署相同的机制来实现。图2-7说明了这种逐一替换控制平面节点和分批替换工作节点的操作。
图2-7:通过更换集群中的节点进行升级
就地升级
就地升级适用于资源受限的环境,在这种环境下,更换节点是不现实的。回滚操作则更加困难,因此风险也更高。但这也可以通过全面的测试来缓解。请记住,生产配置中的Kubernetes是一个高可用系统。如果就地升级是一次一个节点完成的,风险就会降低。因此,如果使用配置管理工具(如Ansible)来执行此升级操作的步骤,请抵制在生产中一次性访问所有节点的诱惑。
对于etcd节点,按照该项目的文档,你将简单地将每个节点下线,并一次对一个节点执行操作系统、etcd和其他软件包的升级,然后将其重新上线。如果在容器中运行etcd,可以考虑在将成员下线之前预先拉出相关的镜像,以减少停机时间。
对于Kubernetes控制平面和工作节点,如果kubeadm用于初始化集群,那么该工具也应该用于升级。上游文档详细说明了从1.13开始的每一个小版本升级如何执行这个过程。冒着听起来像一个坏操作的风险,一如既往地为失败制定计划,尽可能地自动化,并广泛地测试。