13

Kubernetes 3年生产中我们所学到的东西

 3 years ago
source link: http://cloud.51cto.com/art/202009/626792.htm
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

YremI3E.jpg!mobile

本文转载自微信公众号「新钛云服」,作者祝祥 。转载本文请联系新钛云服公众号。

我们于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可以极大地提高您的生产力。

记住,为了技术而技术是没有意义的。

原文:https://medium.com/better-programming/3-years-of-kubernetes-in-production-heres-what-we-learned-44e77e1749c8


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK