写点什么

Kubernetes 用了,延迟高了 10 倍,问题在哪?

  • 2019-11-20
  • 本文字数:4544 字

    阅读完需:约 15 分钟

Kubernetes用了,延迟高了10倍,问题在哪?

当我们团队将业务迁移至 Kubernetes 之后,一旦出现问题,总有人觉得“这是迁移之后的阵痛”,并把矛头指向 Kubernetes。我相信很多朋友肯定听人说过标题里这句话,但最终事实证明犯错的并不是 Kubernetes。虽然文章并不涉及关于 Kubernetes 的突破性启示,但我认为内容仍值得各位管理复杂系统的朋友借鉴。



近期,我所在的团队将一项微服务迁移到中央平台。这套中央平台捆绑有 CI/CD,基于 Kubernetes 的运行时以及其他功能。这项演习也将作为先头试点,用于指导未来几个月内另外 150 多项微服务的进一步迁移。而这一切,都是为了给西班牙的多个主要在线平台(包括 Infojobs、Fotocasa 等)提供支持。


在将应用程序部署到Kubernetes并路由一部分生产流量过去后,情况开始发生变化。Kubernetes 部署中的请求延迟要比 EC2 上的高出 10 倍。如果不找到解决办法,不光是后续微服务迁移无法正常进行,整个项目都有遭到废弃的风险。

为什么 Kubernetes 中的延迟要远高于 EC2?

为了查明瓶颈,我们收集了整个请求路径中的指标。这套架构非常简单,首先是一个 API 网关(Zuul),负责将请求代理至运行在 EC2 或者 Kubernetes 中的微服务实例。在 Kubernetes 中,我们仅代表和 NGINX Ingress 控制器,后端则为运行有基于 Spring 的 JVM 应用程序的 Deployment 对象。


                          EC2                          +---------------+                        |  +---------+  |                        |  |         |  |                     +-------> BACKEND |  |                    |    |  |         |  |                    |    |  +---------+  |                                      |    +---------------+         +------+  |Public       |      |  |      -------> ZUUL +--+traffic      |      |  |              Kubernetes             +------+  |    +-----------------------------+                       |    |  +-------+      +---------+ |                       |    |  |       |  xx  |         | |                       +-------> NGINX +------> BACKEND | |                            |  |       |  xx  |         | |                            |  +-------+      +---------+ |                            +-----------------------------+
复制代码


问题似乎来自后端的上游延迟(我在图中以「xx」进行标记)。将应用程序部署至 EC2 中之后,系统大约需要 20 毫秒就能做出响应。但在 Kubernetes 中,整个过程却需要 100 到 200 毫秒。


我们很快排除了随运行时间变化而可能出现的可疑对象。JVM 版本完全相同,而且由于应用程序已经运行在 EC2 容器当中,所以问题也不会源自容器化机制。另外,负载强度也是无辜的,因为即使每秒只发出 1 项请求,延迟同样居高不下。另外,GC 暂停时长几乎可以忽略不计。


我们的一位 Kubernetes 管理员询问这款应用程序是否具有外部依赖项,因为 DNS 解析之前就曾引起过类似的问题,这也是我们目前找到的可能性最高的假设。

可能原因

假设一:DNS 解析

在每一次请求时,我们的应用程序都像域中的某个 AWS ElasticSearch 实例(例如 elastic.spain.adevinta.com)发出 1 到 3 条查询。我们在容器中添加了一个 shell,用于验证该域名的 DNS 解析时间是否过长。


来自容器的 DNS 查询结果:


[root@be-851c76f696-alf8z /]# while true; do dig "elastic.spain.adevinta.com" | grep time; sleep 2; done;; Query time: 22 msec;; Query time: 22 msec;; Query time: 29 msec;; Query time: 21 msec;; Query time: 28 msec;; Query time: 43 msec;; Query time: 39 msec
复制代码


来自运行这款应用程序的 EC2 实例的相同查询结果:


bash-4.4# while true; do dig "elastic.spain.adevinta.com" | grep time; sleep 2; done;; Query time: 77 msec;; Query time: 0 msec;; Query time: 0 msec;; Query time: 0 msec;; Query time: 0 msec
复制代码


前者的平均解析时间约为 30 毫秒,很明显,我们的应用程序在其 ElasticSearch 上造成了额外的 DNS 解析延迟。


但这种情况非常奇怪,原因有二:


  • Kubernetes 当中已经包含大量与 AWS 资源进行通信的应用程序,而且都没有出现延迟过高的情况。因此,我们必须弄清引发当前问题的具体原因。

  • 我们知道 JVM 采用了内存内的 DNS 缓存。从配置中可以看到,TTL 在 $JAVA_HOME/jre/lib/security/java.security 位置进行配置,并被设置为 networkaddress.cache.ttl = 10。JVM 应该能够以 10 秒为周期缓存所有 DNS 查询。


为了确认 DNS 假设,我们决定剥离 DNS 解析步骤,并查看问题是否可以消失。我们的第一项尝试是让应用程序直接与 ELasticSearch IP 通信,从而绕过域名机制。这需要变更代码并进行新的部署,即需要在/etc.hosts 中添加一行代码以将域名映射至其实际 IP:


34.55.5.111 elastic.spain.adevinta.com
复制代码


通过这种方式,容器能够以近即时方式进行 IP 解析。我们发现延迟确实有所改进,但距离目标等待时间仍然相去甚远。尽管 DNS 解析时长有问题,但真正的原因还没有被找到。


网络管道


我们决定在容器中进行 tcpdump,以便准确观察网络的运行状况。


[root@be-851c76f696-alf8z /]# tcpdump -leni any -w capture.pcap
复制代码


我们随后发送了多项请求并下载了捕捉结果(kubectl cp my-service:/capture.pcap capture.pcap),而后利用 Wireshark 进行检查。


DNS 查询部分一切正常(少部分值得讨论的细节,我将在后文提到)。但是,我们的服务处理各项请求的方式有些奇怪。以下是捕捉结果的截图,显示出在响应开始之前的请求接收情况。



数据包编号显示在第一列当中。为了清楚起见,我对不同的 TCP 流填充了不同的颜色。


从数据包 328 开始的绿色部分显示,客户端(172.17.22.150)打开了容器(172.17.36.147)间的 TCP 连接。在最初的握手(328 至 330)之后,数据包 331 将 HTTP GET /v1/…(传入请求)引向我们的服务,整个过程耗时约 1 毫秒。


来自数据包 339 的灰色部分表明,我们的服务向 ElasticSearch 实例发送了 HTTP 请求(这里没有显示 TCP 握手,是因为其使用原有 TCP 连接),整个过程耗费了 18 毫秒。


到这里,一切看起来还算正常,而且时间也基本符合整体响应延迟预期(在客户端一侧测量为 20 到 30 毫秒)。


但在两次交换之间,蓝色部分占用了 86 毫秒。这到底是怎么回事?在数据包 333,我们的服务向/latest/meta-data/iam/security-credentials 发送了一项 HTTP GET 请求,而后在同一 TCP 连接上,又向/latest/meta-data/iam/security-credentials/arn:…发送了另一项 GET 请求。


我们进行了验证,发现整个流程中的每项单一请求都发生了这种情况。在容器内,DNS 解析确实有点慢(理由同样非常有趣,有机会的话我会另起一文详加讨论)。但是,导致高延迟的真正原因,在于针对每项单独请求的 AWS Instance Metadata Service 查询。

假设二:指向 AWS 的流氓调用

两个端点都是 AWS Instance Metadata API 的组成部分。我们的微服务会在从 ElasticSearch 中读取信息时使用该服务。这两条调用属于授权工作的基本流程,端点通过第一项请求产生与实例相关的 IAM 角色。


/ # curl http://169.254.169.254/latest/meta-data/iam/security-credentials/arn:aws:iam::<account_id>:role/some_role
复制代码


第二条请求则向第二个端点查询实例的临时凭证:


/ # curl http://169.254.169.254/latest/meta-data/iam/security-credentials/arn:aws:iam::<account_id>:role/some_role`{    "Code" : "Success",    "LastUpdated" : "2012-04-26T16:39:16Z",    "Type" : "AWS-HMAC",    "AccessKeyId" : "ASIAIOSFODNN7EXAMPLE",    "SecretAccessKey" : "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",    "Token" : "token",    "Expiration" : "2017-05-17T15:09:54Z"}
复制代码


客户可以在短时间内使用这些凭证,且端点会定期(在 Expiration 过期之前)检索新凭证。这套模型非常简单:出于安全原因,AWS 经常轮换临时密钥,但客户端可以将密钥缓存几分钟,从而抵消检索新凭证所带来的性能损失。


照理来说,整个过程应该由 AWS Java SDK 为我们处理。但不知道为什么,实际情况并非如此。搜索了一遍 GitHub 问题,我们从#1921当中找到了需要的线索。


AWS SDK 会在满足以下两项条件之一时刷新凭证:


  • Expiration 已经达到 EXPIRATION_THRESHOLD 之内,硬编码为 15 分钟。

  • 最后一次刷新凭证的尝试大于 REFRESH_THRESHOLD,硬编码为 60 分钟。


我们希望查看所获取凭证的实际到期时间,因此我们针对容器 API 运行了 cURL 命令——分别指向 EC2 实例与容器。但容器给出的响应要短得多:正好 15 分钟。现在的问题很明显了:我们的服务将为第一项请求获取临时凭证。由于有效时间仅为 15 分钟,因此在下一条请求中,AWS SDK 会首先进行凭证刷新,每一项请求中都会发生同样的情况。


为什么凭证的过期时间这么短?


AWS Instance Metadata Service 在设计上主要代 EC2 实例使用,而不太适合 Kubernetes。但是,其为应用程序保留相同接口的机制确实很方便,所以我们转而使用 KIAM,一款能够运行在各个 Kubernetes 节点上的工具,允许用户(即负责将应用程序部署至集群内的工程师)将 IAM 角色关联至 Pod 容器,或者说将后者视为 EC2 实例的同等对象。其工作原理是拦截指向 AWS Instance Metadata Service 的调用,并利用自己的缓存(预提取自 AWS)及时接上。从应用程序的角度来看,整个流程与 EC2 运行环境没有区别。


KIAM 恰好为 Pod 提供了周期更短的临时凭证,因此可以合理做出假设,Pod 的平均存在周期应该短于 EC2 实例——默认值为 15 分钟。如果将两种默认值放在一起,就会引发问题。提供给应用程序的每一份证书都会在 15 分钟之后到期,但 AWS Java SDK 会对一切剩余时间不足 15 分钟的凭证做出强制性刷新。


结果就是,每项请求都将被迫进行凭证刷新,这使每项请求的延迟提升。接下来,我们又在 AWS Java SDK 中发现了一项功能请求,其中也提到了相同的问题。


相比之下,修复工作非常简单。我们对 KIAM 重新配置以延长凭证的有效期。在应用了此项变更之后,我们就能够在不涉及 AWS Instance Metadata Service 的情况下开始处理请求,同时返回比 EC2 更低的延迟水平。

总结

根据我们的实际迁移经验,最常见的问题并非源自 Kubernetes 或者该平台其他组件,与我们正在迁移的微服务本身也基本无关。事实上,大多数问题源自我们急于把某些组件粗暴地整合在一起。


我们之前从来没有复杂系统的整合经验,所以这一次我们的处理方式比较粗糙,未能充分考虑到更多活动部件、更大的故障面以及更高熵值带来的实际影响。


在这种情况下,导致延迟升高的并不是 Kubernetes、KIAM、AWS Java SDK 或者微服务层面的错误决策。相反,问题源自 KIAM 与 AWS Java SDK 当中两项看似完全正常的默认值。单独来看,这两个默认值都很合理:AWS Java SDK 希望更频繁地刷新凭证,而 KIAM 设定了较低的默认过期时间。但在二者结合之后,却产生了病态的结果。是的,各个组件能够正常独立运行,并不代表它们就能顺利协作并构成更庞大的系统。


原文链接:


https://srvaroa.github.io/kubernetes/migration/latency/dns/java/aws/microservices/2019/10/22/kubernetes-added-a-0-to-my-latency.html


2019-11-20 11:039625
用户头像
赵钰莹 极客邦科技 总编辑

发布了 884 篇内容, 共 652.5 次阅读, 收获喜欢 2680 次。

关注

评论 1 条评论

发布
用户头像
nice debug process:)
2019-11-27 23:42
回复
没有更多了
发现更多内容

搞不懂云原生

二哥不再迷茫

快手、知乎等平台严厉打击“病媛”炒作行为:自媒体行业不能被流量裹挟

石头IT视角

【新品尝鲜】OCR磅单识别上线邀测,为货运物流提速增效

百度大脑

人工智能 OCR

音视频终端引擎优化实践

百度开发者中心

最佳实践 音视频 实践案例 智能视频 行业深度

不得不聊一聊英语爱好者的单词杂货铺了

小匚

随笔杂谈 生活记录 英语 大学英语四六级 单词

东软熙康、百度、京东方,互联网医院的三个典型样本

海比研究院

如何做一场有趣又高效的迭代回顾会议?

万事ONES

Scrum 敏捷 回顾会

重磅来袭,虚拟化技术分类

hanaper

华为云带你探秘Xtrabackup备份原理和常见问题分析

华为云数据库小助手

GaussDB 华为云数据库 MySQL 数据库 GaussDB(for MySQL)

史上最全Java高频面试合集,命中率高达95%

Java 程序员 架构 面试 后端

艾瑞发布《2021年中国企业级 SaaS 行业研究报告》,ONES 入选典型厂商案例

万事ONES

项目管理 SaaS 协同办公

智能大数据专场,百度智能云带来智能大数据产品架构全景图

百度大脑

人工智能 大数据

浪潮云洲荣获两项工业自动化及数字化行业年度大奖 inspur浪潮云 昨天

云计算

超十年渗透专家总结出636页渗透测试全笔记,100课时一次讲清

Java 架构 面试 程序人生 编程语言

掘金热榜第一!阿里P8用近十个月整理出来999页Java岗核心笔记限时开源!

Java 架构 面试 程序人生 编程语言

从工具、工具箱到数字化软件工厂——DevOps 设计理念与工程实践专场 | CIF 精彩看点

CODING DevOps

DevOps 数字化 研发工具 腾讯云 CIF 峰会

用Python绘制专业的K线图【含源代码】

恒生LIGHT云社区

拥抱开源,共建生态 - 开源生态与效能提升专场 | CIF 精彩看点

CODING DevOps

DevOps 研发效能 腾讯云 CIF 峰会 开源生态

目睹阿里技术官写的Tomcat架构笔记后,瞬间觉得自己是渣渣

Java 架构 面试 程序人生 编程语言

阿里巴巴架构师十年整理出的JavaSpringBoot核心文档,真是太全了

Java 架构 面试 程序人生 编程语言

四面字节跳动(高级开发岗):分布式+中间件+TCP+JVM+Hashmap

Java 编程 架构 面试 计算机

智能网联汽车行业信息安全现状与威胁

SOA开发者

如何让文件共享 SDK 支持使用 Uri 上传文件

ZEGO即构

文件存储 分区存储 文件共享

图谱相关技术在风控反作弊中的应用和探索

百度Geek说

后端 软件架构

AlibabaP9力荐!最新出品1297页JDK源码+并发核心原理解析小册

Java 架构 面试 程序人生 编程语言

手把手教你使用Studio Lite + Digtal car!助力智能汽车场景、轻应用开发更轻松!

SOA开发者

使用 grpcurl 通过命令行访问 gRPC 服务

AlwaysBeta

golang gRPC

提高工作效率的三种方法

石云升

团队管理 管理 引航计划 内容合集 9月日更

金九银十不要怕!有了腾讯这本2021年最新Java面试手册,offer手到擒来!

Java 程序员 架构 面试 后端

互斥锁、自旋锁、读写锁...理清它们的区别和应用

行云创新

云计算 编程 开发 应用

行云创新:云原生技术助力企业数字化转型

行云创新

技术 云原生 转型 数字化 平台

Kubernetes用了,延迟高了10倍,问题在哪?_容器_Galo Navarro_InfoQ精选文章