换个姿势学习Kubernetes运营,如何5个月在生产环境构建K8S?

导读:

在分布式系统上管理服务是运维团队面临的最困难的问题之一。在生产中突破新软件并学习如何可靠地运营是非常重要的。本文是一则实例,讲述为什么学习运营Kubernetes很重要,以及为什么很难。本文是关于Kubernetes bug导致的一小时中断故障的事后剖析。

为什么选择在Kubernetes之上构建?如何将Kubernetes集成到现有基础设施中?本文作者给出的方法是建立 (和改进) 对Kubernetes集群的可靠性的信任,以及构建在Kubernetes之上的抽象。 我们最近在Kubernetes之上构建了一个分布式的cron作业调度系统,这是一个令人兴奋的容器编排的新平台。Kubernetes现在非常流行,并且有许多令人兴奋的承诺:最令人兴奋的是,程序员不需要知道或关心他们的应用程序运行的是什么机器。

什么是Kubernetes?

Kubernetes是一个分布式系统,用于调度程序在集群中运行。你可以告诉Kubernetes运行一个程序的5个副本,它将在工作节点上动态调度它们。容器自动调度以增加利用率,节省资金,强大的deployment primitives允许逐步推出新的代码,安全上下文和网络策略允许企业以安全的方式运行多租户的工作负载。

Kubernetes有很多不同类型的调度能力。它可以调度长时间运行的HTTP服务、在集群中每台机器上运行的daemonsets、每小时运行的cron作业等等。
为什么是Kubernetes? 每个基础设施项目都是从业务需求开始的,我们的目标是提高现有分布式cron作业系统的可靠性和安全性。我们的要求是:

建立和运营一支小团队(只有2人在项目中全职工作)。 在20台机器上可靠地安排大约500个不同的cron作业。

我们决定在Kubernetes之上建立的几个原因:

希望构建一个现有的开源项目。

kubernetes包含一个分布式cron作业调度器,不必自己编写。

kubernetes是一个非常活跃的项目,经常接受捐赠。

kubernetes是用Go写的,很容易学。几乎所有Kubernetes的bug都是由团队中没有经验的程序员做的。

如果我们能够成功地运营Kubernetes,可以在未来的Kubernetes上构建,例如,目前正在开发基于kubernet的系统来训练机器学习模型。

我们以前使用Chronos作为cron作业调度系统,但它不再是满足可靠性要求,而且大部分都没有维护(在过去9个月中1次提交, 最后一次合并请求的时间是2016年3月))Chronos未维护的,我们认为不值得继续投资改善现有的集群。

如果你正考虑Kubernetes,请记住:不要仅仅因为其他公司在使用Kubernetes而使用它。建立一个可靠的集群需要花费大量的时间,使用它的业务案例并不是很突出。把你的时间用在聪明的方法上。 可靠性是什么意思? 说到运营服务,“可靠”这个词本身并没有什么意义。要讨论可靠性,首先需要建立一个SLO(服务级别目标)。

我们有三个主要目标:

99.99%的cron作业应该在预定运行时间的20分钟内开始运行。20分钟是一个很宽的窗口,但是我们采访了内部客户,没有人要求更高的精确度。
Jobs应该运行99.99%的时间(不被终止)。

向Kubernetes的迁移不会导致任何面向客户的事件。

这意味着:

Kubernetes API的短暂停机时间是可以接受的(如果停机10分钟,只要在5分钟内恢复即可)。

调度错误(cron作业运行完全丢失并且根本无法运行)是不可接受的。我们非常重视安排错误报告。

要谨慎对待pod evictions 和安全终止实例,以免作业过于频繁地终止。 需要一个好的迁移计划。

建立一个Kubernetes集群

我们建立第一个Kubernetes集群的基本方法是从零开始构建集群,而不是使用kubeadm或kops之类的工具。使用Puppet(常用的配置管理工具)调配了配置。从头开始构建很好,原因有两个:能够深入地集成Kubernetes在架构中,并且深入理解其内部。

我们希望将Kubernetes整合到现有的基础架构中。与现有系统无缝集成,以便进行日志记录,证书管理,加密,网络安全,监控,AWS实例管理,部署,数据库代理,内部DNS服务器,配置管理以及更多。整合所有这些系统有时需要一点创造力,但总体上比试图让kubeadm / kops成为我们想要的更容易。

在信任并了解如何操作这些现有系统后,我们希望继续在新的Kubernetes群集中使用。例如,安全证书管理是一个非常棘手的问题,已经有办法颁发和管理证书。通过适当的整合,我们避免了为Kubernetes创建新的CA。

准确了解设置的参数是如何影响Kubernetes设置的。例如,在配置用于身份验证的证书/CAs时,使用了超过12个参数。了解这些参数有助于在遇到身份验证问题时更容易调试设置。 对Kubernetes建立信心 在Kubernetes之初,团队中没有人使用过Kubernetes。如何从“没有人用过Kubernetes”到“我们有信心在生产中运行Kubernetes”?

战略0:与其他公司交谈

我们向其他公司询问了Kubernetes的经历。 他们都以不同的方式或在不同的环境中使用Kubernetes(运行HTTP服务,裸机,Google Kubernetes引擎等)。

在谈到Kubernetes这样庞大而复杂的系统时,重要的是认真思考自己的用例,做自己的实验,建立对自己环境的信心,并做出决定。 例如,你不该读这篇博客文章并得出结论:“Stripe正在成功使用Kubernetes,所以它也适用于我们!”

以下是我们在与几家运营Kubernetes集群的公司沟通后后学到的:

优先考虑企业etcd集群的可靠性(etcd是存储所有Kubernetes集群状态的地方)。 某些Kubernetes功能比其他功能更稳定,因此请小心Alpha功能。一些公司只有在稳定后才能使用稳定特性(例如,如果某个功能在1.8版本中保持稳定,则在使用它之前会等待1.9或1.10)。 考虑使用托管的Kubernetes系统,如GKE / AKS / EKS。从头开始建立高可用性Kubernetes系统是一项巨大的工作。 AWS在此项目中没有托管的Kubernetes服务,所以这不适合我们。 注意由覆盖网络/软件定义网络引入的额外网络延迟。

策略1:阅读代码。

我们计划很大程度上依赖于Kubernetes的一个组件,即cronjob控制器。这个组件当时处于alpha阶段,这让我们有点担心。我们在一个测试集群中尝试了它,但是如何判断它在生产中是否适合我们呢?

值得庆幸的是,所有cronjob控制器的核心功能只有400行Go。通过源代码快速读取显示:

cron作业控制器是一个无状态的服务(与其他Kubernetes组件一样,除了etcd)。
每10秒钟,这个控制器调用syncAll函数:go wait.Until(jm.syncAll,10 * time.Second,stopCh) syncAll函数从Kubernetes API中获取所有cron作业,遍历该列表,确定下一步应该运行哪些作业,然后启动这些作业。

核心逻辑似乎相对容易理解。更重要的是,如果在这个控制器中有一个bug,它可能是我们可以修复的东西。

策略2:做负载测试

在开始认真构建集群之前,我们做了一些负载测试。我们并不担心Kubernetes集群能够处理多少节点(计划部署大约20个节点),但是确实想让某些Kubernetes能够处理我们希望运行的那么多的cron作业(大约每分钟50个)。

在一个3节点集群中运行了测试,创建了1000个cron作业,每个任务每分钟运行一次。这些工作中的每一个都简单地运行bash -c 'echo hello world'。我们选择简单的作业,是因为希望测试集群的调度和编排能力,而不是集群的总计算能力。

测试集群无法处理每分钟1000个cron作业。每个节点每秒最多只能启动一个pod,而集群能够每分钟运行200个cron作业。由于我们只希望每分钟运行大约50个cron作业,所以我们认为这些限制不是阻碍因素。

策略3:优先构建和测试高可用性etcd集群。

在设置Kubernetes时,最重要的事情之一就是运行etcd。Etcd是Kubernetes集群的核心,它是存储集群中所有数据的地方。除了etcd之外,其他一切都是无状态的。如果etcd没有运行,不能对Kubernetes集群进行任何更改(尽管现有的服务将继续运行!)

这张图显示了etcd是Kubernetes集群的核心——API服务器是etcd前面的无状态REST/认证端点,然后其他组件通过API服务器与etcd对话。 在运行时,有两个要点需要牢记:

设置复制,这样集群不会死如果你失去了一个节点。我们现在有三个etcd副本。 确保足够的I / O带宽。我们的etcd版本有一个问题,一个具有高fsync延迟的节点可能触发连续的leader elections,导致集群无法使用。通过确保所有节点的I/O带宽都比etcd的写入数量多,从而弥补了这一点。

设置复制不是一个设置-忘记操作。我们仔细地测试后发现可能会丢失一个etcd节点,并且集群优雅地恢复了。

以下是为建立etcd集群所做的一些工作:

设置复制 监控etcd服务是可用的 写一些简单的工具,以便轻松创建新的etcd节点,并加入到集群当中 编写一些简单的工具,以便我们可以轻松创建新的etcd节点并将它们加入到群集中 补丁etcd的高集成,这样我们可以在生产环境中运行超过1个 etcd集群 测试从一个etcd备份中恢复 测试可以在不停机情况下重建整个集群

很高兴很早就做了这个测试。某个周五的早晨,在我们的生产集群中,一个etcd节点停止了对ping的响应。我们得到了警报,终止了节点,带来了一个新的节点,加入到集群中,同时Kubernetes继续运行。

策略4:逐步将工作迁移到Kubernetes

我们的目标之一是将工作迁移到Kubernetes而不造成任何中断。成功进行生产迁移的秘诀不是避免犯错(这是不可能的),而是设计你的迁移以减少错误的影响。

我们很幸运有多种职位可以迁移到新集群,所以可以迁移一些低影响的工作岗位,接受一两次失败。

在开始迁移之前,构建了易于使用的工具,如果有必要,可以在不到五分钟的时间内在旧系统和新系统之间来回移动作业。这种简单的工具减少了错误的影响 - 如果迁移一个没有计划的依赖的工作,没有什么大不了的!可以将其移回原处,解决问题,然后再试。

以下是我们采用的整体迁移策略:

根据他们的重要程度大致排序 将一些重复的工作迁移到Kubernetes。如果发现新的情况,快速回滚,修复问题,然后重试。

策略5:调查 Kubernetes bug 并修复它们

我们在项目开始时制定了一个规则:如果Kubernetes做了一些意外的事情,必须调查,找出原因,并提出补救措施。

调查每个问题都很耗时,但很重要。如果只是简单地将Kubernetes的”古怪行为”看作是复杂的分布式系统的功能,我们担心,因为它们会被调用导致产生bug集群。

在使用了这种方法之后,我们发现并且修复了Kubernetes的几个bug。

以下是测试中发现的一些问题:

名称超过52个字符的Cronjob无法安排作业。 Pods有时会永远停留在挂起状态。
调度程序会每3个小时崩溃一次。 Flannel的hostgw后端没有替换过时的路由表项

修复这些bug让我们对Kubernetes项目的使用感觉好得多——不仅它运行得比较好,而且也接受补丁并有一个良好的PR审查过程。

Kubernetes有bug,像所有的软件一样。特别是,我们非常频繁地使用调度器(cron作业总是在创建新的pods),而调度器使用缓存有时会导致bug、回退和崩溃。缓存是困难的!但是代码库是可接近的,我们已经能够处理遇到的bug。

值得一提的是,Kubernetes的pod驱逐逻辑。Kubernetes有一个称为节点控制器的组件,它负责将pod驱逐出去,如果节点没有响应,则将它们移到另一个节点。allnodes会暂时无响应(例如,由于网络或配置问题),在这种情况下,Kubernetes可以终止集群中的所有pod。

如果运行的是大型Kubernetes集群,请仔细阅读节点控制器文档,仔细地考虑设置,并进行广泛测试。每次通过创建网络分区测试对这些设置的配置更改(例如,pod-驱逐超时),就会发生令人惊讶的事情。最好在测试中发现这些意外,而不是在生产中发现。

策略6:有意引起Kubernetes集群问题

之前讨论过在Stripe中进行游戏日练习。这个想法是要想出你最终会在生产中发生的情况,然后在生产中故意造成这些情况,从而确保能够处理它们。

在集群上进行了几次练习之后,经常发现诸如监视或配置错误等问题。很高兴在早期发现这些问题,而不是六个月后突然发现。

以下是运行的一些比赛日练习:

终止Kubernetes API服务器 终止所有Kubernetes API服务器并将其恢复(这非常有效) 终止etcd节点 从API服务器中关闭Kubernetes集群中的工作节点(以便它们无法通信)。这导致节点上的所有pods被迁移到其他节点。

很高兴看到Kubernetes如何应对我们投入的大量干扰。 Kubernetes的设计是为了适应错误 - 它有存储所有状态的etcd集群,一个只是该数据库的REST接口的API服务器,以及一个协调所有集群管理的无状态控制器集合。

如果任何Kubernetes核心组件(API服务器,控制器管理器或调度程序)被中断或重新启动,一旦它们出现,它们将从etcd读取相关状态并继续无缝运行。这是我们希望的事情之一,而且在实践中实际运作良好。

以下是测试中发现的一些问题:

“没有得到paged,来修复监控。“ “当销毁API服务器实例并将其恢复后,需要人工干预。最好解决这个问题。“ “有时执行etcd故障转移时,API服务器会启动超时请求,直至重新启动。”

在运行这些测试后,针对发现的问题开发了补救措施:改进了监控,发现了固定配置问题,并提交了Kubernetes bug。 使cron作业易于使用 简单地探讨一下我们是如何使基于kubernetes的系统易于使用的。

最初的目标是设计一个运行cron作业的系统,团队有信心运营和维护。一旦建立了对Kubernetes的信心,就需要工程师们轻松地配置和增加新的cron作业。我们开发了一个简单的YAML配置格式,这样用户就不需要了解Kubernetes的内部结构来使用这个系统。这是我们开发的格式:

name: job-name-here
kubernetes:
schedule: '15 */2 * * *' command:
- ruby - "/path/to/script.rb" resources:
requests: cpu: 0.1 memory: 128M limits: memory: 1024M

没有做什么特别的事情——我们编写了一个简单的程序,将这种格式转换为Kubernetes cron作业配置,将其应用于kubectl。

我们还编写了一个测试套件,以确保作业名称不会太长,并且所有名称都是惟一的。我们目前不使用cgroups来强化对大多数作业的内存限制,但计划将来推出。

我们的简单格式易于使用,而且由于自动生成了来自相同格式的Chronos和Kubernetes cron作业定义,所以在两个系统之间迁移作业非常简单。这是使我们的增量迁移工作良好的关键部分。将作业迁移到Kubernetes时,可以用一个简单的三行配置更改,在不到十分钟的时间内将其移回。 监控Kubernetes 监测Kubernetes集群的内部状态非常令人愉悦。我们使用kube-state-metrics软件包进行监测,并使用一个名为veneurl - Prometheus的小型Go程序来获取Prometheus的度量标准,将它们作为statsd指标发布到我们的监控系统中。

例如,以下是过去一小时内集群中未决Pod的数量图表。Pending意味着等待分配一个工作节点来运行。可以看到上午11点的数字峰值,很多cron作业在每小时的第0分钟运行。

还有一个监视器,用于检查有没有任何Pod在Pending状态中卡住 - 每个Pod在5分钟内开始在worker节点上运行,否则会收到警报。 Kubernetes未来计划
设置Kubernetes,到顺畅地运行生产代码并将所有cron作业迁移到新集群,花了五个月的时间,三位工程师全职工作。我们投资学习Kubernetes的一个重要原因是希望能够在Stripe中更广泛地使用Kubernetes。

以下是适用于运行Kubernetes(或任何其他复杂分布式系统)的原则:

为企业的Kubernetes项目,以及所有基础设施项目,定义清晰的商业原因。了解业务案例和用户的需求使项目变得更加容易。 积极削减范围。避免使用许多Kubernetes的基本特性来简化集群。这让我们可以更快速地发送消息,例如,由于pod-to-pod联网不是我们项目的必需条件,可以关闭节点之间的所有网络连接,并将Kubernetes的网络安全性推迟。

花大量时间学习如何正确地运营Kubernetes集群。仔细测试边界情况。分布式系统非常复杂,有很多潜在的问题。以前面的例子为例:如果节点控制器由于配置与API服务器失去联系,那么它可以杀死集群中的所有pods。学习Kubernetes在每次配置更改后的表现如何,需要时间和精心的关注。

通过专注于这些原则,我们已经有信心在生产中使用Kubernetes。我们将继续开发Kubernetes的使用,例如,我们正在关注AWS EKS的发布。我们正在完成另一个系统的工作,训练机器学习模型,并且正在探索将一些HTTP服务迁移到Kubernetes。随着我们继续在生产中运行Kubernetes时,我们计划对开源项目做出贡献。

原文作者:Julia Evans

原文链接:

Learning to operate Kubernetes reliably
https://stripe.com/blog/operating-kubernetes

添加小数微信:xiaoshu062 备注公司、姓名、职位 小数将拉您进入相应技术群