去年年底,我们尝试在已有的 Kubernetes 集群上部署大约 1000 个OpenStack 节点,试图发现可能遇到的问题,然后尽可能找到修复它们的方法。总的来说是有些问题,但一般情况下我们能解决,我们觉得这些经验是大家可能在寻找的。
整体而言,我们使用 Kargo 部署了 Kubernetes 集群,并用 Fuel-CCP 在其上又部署了 900 多个节点的 OpenStack 集群。Kargo 是 Kubernetes 孵化项目的一部分,并遵循了大型 Kubernetes 集群 的参考架构。
实验中我们记录了发现的问题,对部署工具、参考设计文档的修改。下面就是我们的发现。
配置
我们一开始使用了 175 个物理服务器,其中 3 个用于 Kubernetes 控制服务(API 服务器,ETCD,Kubernetes 调度器等),剩余的每个节点各有 5 个虚拟机作为 Kubernetes minion 节点。
每台物理服务器的规格如下:
●HP ProLiant DL380 Gen9
●CPU 2x Intel(R) Xeon(R) CPU E5-2680 v3 @ 2.50GHz
●内存 264G
●存储 3.0T on RAID on HP Smart Array P840 Controller, HDD 12 x HP EH0600JDYTL
● 网络 2x Intel Corporation Ethernet 10G 2P X710
运行的 OpenStack 集群(就 Kubernetes 而言)包括:
1.OpenStack 控制服务运行在超过 6 个节点的 150 个 pod 中
2.剩余节点有接近 4500个 pod,每个 minion 节点有5 个 pod
一个 Prometheus 的主要问题
在实验中,我们使用Prometheus 工具来监测资源的消耗,核心系统、Kubernetes、OpenStack 服务的负载。当使用 Prometheus 时有个问题引起我们注意:删除 Prometheus 存储的旧数据会明显提高 Prometheus API 的速度,但是这样旧群集的信息也被删除了,使得后期无法处理数据了。所以在每次测试时都要详细的记录观察到的问题,以及每次调试都要足够充分。
值得庆幸的是,我们做了详细的记录,另外我们决定将 Prometheus 数据备份到它支持的时间序列数据库中来防止这种情况,如 InfluxDB、Cassandra、OpenTSDB。默认情况下,Prometheus 对使用实时监控、报警系统进行了优化,Prometheus 开发团队也官方推荐只保留 15 天的监控数据,以保证系统工作响应速度。通过设置备份,我们可以从备份中获取数据来进行后期处理。
我们测试中遇到的问题
最初,我们把所有节点(包括 Kubernetes 控制平面)都运行在虚拟环境中,但负载是太高以至于不能正常工作,所以他们被转移了物理服务器上。尽管如此,在我们迁移到物理服务器后,Kubernetes 集群中运行的 API 服务器仍占用了 2000% 的 CPU(接近总计算节点性能的 45%)。
所有不在 Kubernetes 主节点上的服务(kubelet、kube-proxy 在所有的 minion 节点上)都通过本地的 Nginx 代理来访问 API 服务。大多数的请求在初始化连接后(设置的超时时间为 5 至10分钟)都是空闲的。而 Nginx 配置中超过 3 秒的空闲连接都会被切断,这导致所有客户端进行重新连接,甚至更糟糕的会重新启动被中止的 SSL 会话。在服务端,Kube API 服务因此消耗了 2000% 的 CPU资源,使得其他请求响应非常慢。
在 nginx.conf 中设置 proxy_timeout 参数为 10分钟,使得 Nginx 切断 SSL 连接前有足够多的时间等待连接本身超时。修改之后,一个 API 服务只消耗了 100% 的 CPU(总计算节点性能的2%),另一个 API 服务消耗了 200% 的 CPU(总计算节点性能的 4%),平均响应时间为 200-400毫秒。
Kargo 部署时设置 proxy_timeout 为 10分钟:问题 已修复 ,代码 由 Fuel CCP 团队提交。
3.2 KubeDNS 默认设置不能处理大型集群的负载
当部署这么大的 OpenStack 集群,KubeDNS 由于负载过高而失去响应。我们从 kubedns pod 的 dnsmasq 容器中的日志里发现了出错的原因:
Maximum number of concurrent DNS queries reached.
此外,dnsmasq 容器有时也会因为内存限制而重新启动。
首先,kubedns 在这个架构中似乎即使没有负载也常常出错。在实验过程中,我们观察到,即使在一个没有 pod(但足够大) 的 Kubernetes 群集中 kubedns 容器也经常重新启动。重新启动是因为心跳检测失败而引起的,在日志中我们还没观察有价值的信息。
其次,dnsmasq 理论上是让 kubedns 负载降低的,但这么大负载的情况下它需要重新调优才能正常工作。
解决这个问题需要几个层次的步骤:
1.放宽 dnsmasq 容器的限制:他们承担了大部分的负载。
2.给 kubedns 冗余控制器添加更多的副本(我们左后使用了 6 个副本,因为它解决了问题 - 更大的集群它可能需要更多的副本)。
3.增加 dnsmasq 的并发处理数量(我们使用 dnsmasq 手册中推荐的 dns-forward-max=1000)
4.增加 dnsmasq 缓存的大小:它设置了 10000 个缓存条目的限制。
5.修复 kubedns 来更合理地处理这种情况。
针对#1和#2 的问题 Kubernetes 团队使得 Kargo 可以修改参数配置:问题 ,代码 。
其他问题 - 还未开始修复。
3.3 Kubernetes 调度器需要部署到单独的节点上
在 Kubernetes 上部署大规模的 OpenStack 集群,Kubernetes 的调度器、控制器、管理器和 API 服务负载都很高,对 CPU 资源的抢夺也很严重。调度器是最繁忙的,所以我们需要把它部署在单独的节点上。
我们把 Kubernetes 调度器手动移动到单独的节点;手动杀死其他调度,以防止它们转移到其它节点上。
在Kargo 中的问题 。
3.4 Kubernetes 调度器处理 pod antiaffinity 非常低效
每次使用 pod antiaffinity 规则调度 pod 时,调度器都要花费很长的时间。每个 pod 的调度时间大约为2-3秒,这使得部署 900 个节点的 OpenStack 集群需要非常长的时间(仅调度大约需要 3 小 时)。OpenStack 的部署必须使用 antiaffinity 规则,以防止单个 Kubernetes minion 节点启动多个 OpenStack 计算服务。
根据分析结果,大部分的时间都花在创造新的选择器来匹配现有的pod,这会触发验证操作。基本上,我们有 O(N^2) 不必要的验证步骤(其中 n = pod 的数目),即使我们只有 5 个部署步骤需要调度。
在这种情况下,我们需要优化调度的时间,使得其调度一个 pod最多需要 300 毫秒。这样调度还是很慢(调度一个 900 个节点的OpenStack 集群需要 30 分钟),但它至少合理。该解决方案降低了耗时的操作到 O(N) 的时间,这比之前的好,但仍取决于 pod的数目而不是部署的数目,因此还有很大的改进空间。
这部分优化已经合并到主仓库(代码 ),以及移植到了 1.5 分支,并在 1.5.2 发布(代码 )。
即使 Kube API 服务器可以承载更多的负载,不同的服务仍收到“429 Rate Limit Exceeded” 的 HTTP 错误。这个问题是在调度器 BUG 中发现(见下文)。
通过 max-requests-inflight 选项来修改 Kube API 服务的最大连接速率。它默认为 400,但在我们的情况下,它设为 2000 比较合理。Kargo部署工具应该可以配置这个数字,因为部署更大规模的集群这个数字需要更大。
在 Kargo 中的问题 。
当创建大量 pod(在我们的例子中大约为 4500 个),并且 Kube API 服务返回了 429 错误(见上文),调度会安排同一部署中的几个 pod 到一个节点上,这违反了它们的 pod antiaffinity 规则。
见下面的代码拉取请求。
来自 Mirantis 团队的修复:代码拉取请求 (已合并,Kubernetes 1.6 的一部分)。
多个节点的 Docker 都会偶尔停止响应,这导致 kubelet 日志中有超时错误。发生这种情况时,受影响的 minion 节点上的 pod 不能被创建和删除。尽管 Docker 1.11 以来很多问题都被修复了,我们还在观察这些现象。
Docker 的日志没有包含有价值的信息,所以我们不得不在受影响的节点上重新启动 Docker 服务。(在实验中,我们使用的是 Docker 1.12.3,但我们在 Docker 1.13rc 版中也观察到了类似的现象。)
3.8 OpenStack 服务不处理 PXC 伪死锁
并行运行时,大量的资源都创建失败了,原因是 DBError:Percona Xtradb Cluster 发现了死锁,事务需要重新开始。
oslo.db 负责处理从数据库收到的错误,并传给相应的类处理。这样,如果发生类似的错误,数据库事务可以重新开始。但是 Percona 的给出的错误 oslo.db 没有正确处理。我们解决了这个问题之后,仍然经历了类似的错误,因为 Nova 代码中不是所有的数据库事务都能正确的重新启动。
该问题 由 Roman Podolyaka 修复(CR) ,并backports 回了 Newton 版。它修复了 Percona 的死锁错误检测,但在 Nova 中至少还有一个地方仍然需要修复。
3.9 live_migration_uri 的配置导致热迁移失败
配置了 live_migration_uri 后,因为一个计算节点上的 libvirt 不能连接另一个计算节点上的,而导致热迁移失败。
我们没有在 live_migration_uri 模板中指定使用 IP 地址,从而它试图用第一个网络接口的地址,而这是 PXE 网络的,不是 libvirt 监听的私有网络。live_migration_inbound_addr 可以解决这个问题,但因为上游 Nova 的一个问题我们不能使用。
一个 Nova 的错误 已经被修复 和移植回 了Newton 版。之后我们切换 到使用 live_migration_inbound_addr。
UMCloud(上海优铭云计算有限公司)是一家中方控股的合资云计算公司,两大股东分别是中国顶尖的中立公有云服务商UCloud和全球排名第一的OpenStack云计算服务商Mirantis。公司成立于2016年,专注于私有云产品方案与服务、存储产品、Mirantis OpenStack培训等业务。
了解更多资讯 关注官方微信