Kubernetes 3年生产中我们所学到的东西
本文转载自微信公众号「新钛云服」,作者祝祥 。转载本文请联系新钛云服公众号。
我们于2017年开始构建第一个基于1.9.4版本的Kubernetes集群。至今,我们已经有两个集群,一个集群在裸机RHEL VM上运行,另一个集群在公有云AWS EC2上运行。
今天,我们的Kubernetes集群由分布在多个数据中心的400多个虚拟机组成。该平台承载着高度可用的核心应用程序和系统,管理拥有近400万个活跃用户的的大型实时系统。
Kubernetes最终使我们的运维变得更轻松,但是这一过程是艰辛的,是范式的转变。不仅仅是我们的技能和工具有了彻底的转变,而且我们的设计和思维也得到了彻底的转变。我们必须采用多种新技术,并进行大量的投入,以提高团队和基础设施的档次和技能。
回顾三年来,Kubernetes在我们的生产中运行了三年,这也是本文将会将的重点内容。
1. 基于Java的APP容器化问题
当涉及微服务和容器化时,很多人都倾向于避免使用Java,这主要是由Java不太友好的内存管理造成的。但是,现在情况发生了变化,并且Java的容器兼容性在过去几年中得到了很大的改善。毕竟,当前大部分的系统都有使用Java程序的 Apache Kafka作为中间件,另外Elasticsearch通常也是在Java程序上运行。
回顾2017-2018年度,我们有一些应用程序在Java 8上运行。它们常常难以理解像Docker这样的容器环境,并且由于堆内存问题和异常的垃圾收集而崩溃。我们了解到,这是由于JVM无法支持Linux cgroups和namespaces,后者是容器化技术的核心。
但是,从那时起,Oracle一直在不断提高Java在容器领域的兼容性。甚至Java 8的后续补丁都引入了实验性JVM标志来解决这些问题:XX:+UnlockExperimentalVMOptions和XX:+UseCGroupMemoryLimitForHeap。
但是,尽管有了这些改进,但不可否认的是,与Python或Go等同类产品相比,Java仍因占用内存和启动时间慢而名声不佳。主要是由JVM的内存管理引起的。
今天,如果我们必须选择Java,我们会确保它的版本是11或更高。我们的Kubernetes内存限制会在比JVM最大堆内存(-Xmx)多1GB,以获得足够的空间。也就是说,如果JVM使用8GB作为堆内存,我们对应用程序的Kubernetes资源限制将是9GB。这样,应用将会更加健康。
2. Kubernetes升级
Kubernetes生命周期管理(如升级或补丁修复)非常麻烦,尤其是在裸机或虚拟机上构建了自己的集群时。对于升级,我们已经意识到最简单的方法是用最新版本重新构建一个集群,并将工作负载从旧的集群迁移到新的集群。进行节点的升级相对而言会更复杂,且结果无法预料。Kubernetes有多个与其一起协调工作的组件需要与Kubernetes升级保持一致。从Docker到像Calico或Flannel这样的CNI插件,你需要小心地把它们拼凑在一起才能工作。尽管像Kubespray、Kubeone、Kops和Kubeaws这样的项目使它更容易维护,但它们都有各自的缺点,且不太通用。
我们在RHEL VM上使用Kubespray构建了集群。Kubespray非常不错,它有基于ansible的构建、添加和删除新节点、升级版本的playbooks,以及我们在生产中操作Kubernetes所需的几乎所有内容。但是,升级的playbook带了一个免责声明,可防止我们跳过次要版本。因此,必须经过所有中间版本才能达到目标版本。简而言之,Kubespray不支持跨版本升级。
以上可得出的结论是,如果您打算使用Kubernetes或已经在使用Kubernetes,请考虑生命周期管理。构建和运行集群相对容易一些,但是Kubernetes的生命周期管理则相对复杂,涉及内容较多。
3.构建和部署
准备重新设计整个构建和部署的pipelines。我们的构建过程和部署必须经历一个基于Kubernete的完整转型。不仅在Jenkins pipelines中进行了大量的重组,还使用了诸如Helm这样的新工具,制定新的git流和构建策略,docker 镜像的tag标签,以及版本化Helm chart。
您不仅需要维护代码,还需要维护Kubernetes部署文件,Docker文件,Docker镜像,Helm Chart的策略,并设计一种将所有这些链接在一起的方法。
经过几次迭代,我们决定采用以下设计。
应用程序代码及其helm chart位于单独的git存储库中。这使我们可以分别对它们进行版本控制。
然后,我们将helm chart版本与应用程序版本一起保存,并使用它来跟踪发布。因此,例如,使用进行app-1.2.0部署charts-1.1.0。如果仅更改Helm values文件,则仅更改chart的补丁程序版本。(例如,从1.1.0到1.1.1)。所有这些版本均由每个存储库中的发行说明规定RELEASE.txt。
我们与构建或修改其代码的Apache Kafka或Redis之类的系统应用程序的工作方式有所不同。也就是说,我们没有两个git存储库,因为Docker tag只是Helm chart版本控制的一部分。如果我们曾经更改了docker tag进行升级,我们将在chart标签中增加主要版本号。
4.Liveliness和Readiness探针(双刃剑)
Kubernetes的liveliness和readiness探测是自动解决系统问题的的优秀特性。他们可以在发生故障时重新启动容器,并将流量从不正常的实例中转移出去。但在某些故障情况下,这些探测可能成为一把双刃剑,影响应用程序的启动和恢复,尤其是消息中间件或数据库等有状态应用程序。
我们的kafka系统就是受害者。我们运行了一个3 broker 3 zookeeper的状态集群,replicationFactor 为3,minInSyncReplica为2。当Kafka在意外系统故障或崩溃后启动时,就会发生问题。这导致它在启动期间运行其他脚本来修复损坏的索引,根据严重程度,该过程可能需要10到30分钟。由于增加了时间,Liveliness将不断失败,从而向Kafka发出终止信号以重新启动。这阻止了Kafka修改索引并完全启动。
唯一的解决方案是配置initialDelaySeconds liveliness探针设置,以在容器启动后延迟探测。但是,当然,问题在于很难对此加以说明。有些恢复甚至需要一个小时,因此我们需要提供足够的空间来解决这一问题。但是增加的越多initialDelaySeconds,恢复速度就越慢,因为在启动失败期间,Kubernetes需要更长的时间来重新启动容器。
因此,择优的方法是评估initialDelaySeconds字段的值,以便更好地平衡您在Kubernetes中寻求的弹性和应用程序在所有故障情况下(磁盘故障、网络故障、系统崩溃等)成功启动所需的时间
更新*:如果您使用的是最新版本,Kubernetes引入了第三种探针类型,称为“启动探针”,以解决此问题*](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/)*。在1.16的alpha版本和1.18的beta版本后是可用的。
在容器启动之前,启动探针将禁用readiness与liveliness检查,以确保应用程序的启动不会中断。
5.对外的External的IP
我们了解到,使用external IP暴露服务会对内核的连接跟踪机制造成巨大负荷。除非规划详细,考虑周全,否则一旦规模上去,那么它只会崩溃。
我们的集群运行在基于Calico CNI和BGP上,作为我们在Kubernetes内部的路由协议,也可以与边缘路由器对接。对于Kubeproxy,我们使用IP Tables模式。我们在Kubernetes中托管着庞大的服务,该服务通过external IP对外暴露,每天处理数百万个连接。由于使用来自软件定义网络的SNAT和masquerading,Kubernetes需要一种机制来跟踪所有这些逻辑流。为此,它使用内核的Conntrack 与 netfilter工具来管理静态IP的这些外部连接,然后将其转换为内部服务IP,最后转换为您的pod IP。转发过程全部通过conntrack表和IP表完成。
然而,这个conntrack表有其局限性。一旦达到这个限制,Kubernetes集群(底层的操作系统内核)将无法再接受新的连接。在RHEL上,你可以这样检查。
$ sysctl net.netfilter.nf_conntrack_count net.netfilter.nf_conntrack_maxnet.netfilter.nf_conntrack_count = 167012 net.netfilter.nf_conntrack_max = 262144
解决这一问题的一些方法是使用边缘路由器连接多个节点,这样您的静态IP的传入连接遍及整个群集。因此,如果您的集群有大量的机器,那么累积起来就可以有一个大的conntrack表来处理大量传入的连接。
早在2017年成立之初,这一切就让我们望而却步,但最近,Calico在2019年对此进行了详细研究(https://www.projectcalico.org/when-linux-conntrack-is-no-longer-your-friend/)。
6.您真的需要Kubernetes吗?
三年过去了,我们仍然每天都在不断地发现和学习新的东西。kubernetns是一个复杂的平台,有其自身的一系列挑战,特别是构建和维护环境方面的。它将改变你的设计、思维、架构,并将需要提高你的团队能力以满足架构转型。
然而,如果你在云端并且能够将Kubernetes用作“service”,它可以减轻平台维护带来的大部分开销,例如“如何扩展内部网络CIDR?”。或“如何升级我的Kubernetes版本?”
今天,我们意识到你需要问自己的第一个问题是“你真的需要Kubernetes吗?”。这可以帮助您评估您所遇到的问题以及Kubernetes解决该问题的重要性。
Kubernetes转型并没有想象中的那么简单,那么廉价。您为此付出的代价必须真正证明“您的”应用及其如何利用该平台。如果满足这些条件,那么Kubernetes可以极大地提高您的生产力。