我们团队在业务迁移到Kubernetes的时候,一旦出现问题,总有人认为“这就是迁移后的痛苦”,把矛头指向Kubernetes,但最后发现不是Kubernetes那犯了错误。虽然文章没有涉及到关于Kubernetes的突破性爆料,但我觉得内容还是值得管理复杂系统的朋友学习的。最近,我的团队将微服务迁移到中央平台。这个中央平台捆绑了CI/CD、基于Kubernetes的运行时和其他功能。这项工作还将作为试点,指导未来几个月内进一步迁移150多个额外的微服务。所有这一切,都是为了支持西班牙的几个主要在线平台,包括Infojobs、Fotocasa等。将应用程序部署到Kubernetes并将部分生产流量路由到那里后,情况开始发生变化。Kubernetes部署中的请求延迟比EC2高10倍。如果找不到解决方案,不仅后续的微服务迁移会失败,整个项目都有被放弃的风险。为什么Kubernetes中的延迟比EC2高得多?为了查明瓶颈,我们收集了整个请求路径上的指标。该架构非常简单,从一个API网关(Zuul)开始,它负责将请求代理到运行在EC2或Kubernetes中的微服务实例。在Kubernetes中,我们只表示和NGINXIngresscontroller,后端是一个运行基于Spring的JVM应用程序的Deployment对象。EC2+----------------+|+--------+|||||+------>后端|||||||||+---------+||+-------------++-----+|公共|||------->ZUUL+--+raffic|||Kubernetes+------+|+-------------------------+||+------++--------+|||||xx|||+------>NGINX+------>后端|||||xx||||+--------++--------+|+-----------------------------+问题似乎来自后端的上游延迟(我在图中用“xx”标记)。将应用程序部署到EC2中后,系统需要大约20毫秒的时间进行响应。但在Kubernetes中,整个过程需要100到200毫秒。我们很快排除了可能在运行时发生变化的可疑对象。JVM版本完全一样,而且应用程序已经在EC2容器中运行,所以问题不可能出在容器化机制上。此外,负载强度是无害的,因为即使每秒只有1个请求,延迟仍然很高。此外,GC暂停几乎可以忽略不计。我们的一位Kubernetes管理员询问应用程序是否具有外部依赖性,因为DNS解析之前已经导致类似的问题,这是迄今为止我们能找到的最有可能的假设。假设1:DNS解析对于每个请求,我们的应用程序都会对域中的AWSElasticSearch实例(例如elastic.spain.adevinta.com)进行1到3次查询。我们在容器中添加了一个shell来验证域名的DNS解析时间不会太长。来自容器的DNS查询结果:[root@be-851c76f696-alf8z/]#whiletrue;dodig"elastic.spain.adevinta.com"|greptime;sleep2;done;;Querytime:22msec;;Querytime:22msec;;Querytime:29msec;;Querytime:21msec;;Querytime:28msec;;Querytime:43msec;;Querytime:39msec来自运行此应用程序的EC2实例的相同查询结果:bash-4.4#whiletrue;dodig"elastic.spain.adevinta.com"|greptime;sleep2;done;;Querytime:77msec;;Querytime:0msec;;Querytime:0msec;;Querytime:0msec;;Querytime:0msec前者的平均解析时间约为30毫秒,显然,我们的应用在它造成了额外的DNS解析ElasticSearch延迟。但这种情况非常奇怪,原因有二:Kubernetes已经包含了大量与AWS资源通信的应用程序,而且没有一个出现过高的延迟。所以我们必须弄清楚到底是什么导致了当前的问题。我们知道JVM使用内存中的DNS缓存。从配置中可以看出,TTL配置在$JAVA_HOME/jre/lib/security/java.security,设置为networkaddress.cache.ttl=10。JVM应该能够每10秒缓存一次所有DNS查询.为了证实DNS假设,我们决定去掉DNS解析步骤,看看问题是否会消失。我们的第一个尝试是让应用程序直接与ElasticSearchIP通信,从而绕过域名机制。这需要更改代码和新部署,这需要在/etc.hosts中添加一行以将域名映射到其实际IP:34.55.5.111elastic.spain.adevinta.com执行IP解析。我们发现延迟确实有所改善,但仍远未达到目标延迟。虽然DNS解析时间有问题,但一直没有找到真正的原因。网络管道我们决定在容器中执行tcpdump以准确了解网络的健康状况。[root@be-851c76f696-alf8z/]#tcpdump-leniany-wcapture.pcap然后我们发送了多个请求并下载了捕获结果(kubectlcpmy-service:/capture.pcapcapture.pcap),然后使用Wireshark进行检查.DNS查找部分一切正常(有一些细节值得讨论,我稍后会谈到)。但是,我们的服务处理单个请求的方式有些奇怪。下面是捕获结果的屏幕截图,显示了在响应开始之前收到的请求。数据包编号显示在第一列中。为了清楚起见,我为不同的TCP流填充了不同的颜色。以数据包328开头的绿色部分显示客户端(172.17.22.150)在容器(172.17.36.147)之间打开了TCP连接。在初始握手(328到330)之后,数据包331将HTTPGET/v1/..(传入请求)定向到我们的服务,这大约需要1毫秒。数据包339的灰色部分显示我们的服务向ElasticSearch实例发送了一个HTTP请求(这里没有显示TCP握手,因为它使用旧的TCP连接),整个过程耗时18毫秒。到目前为止,一切看起来都很正常,并且时间大致符合整体响应延迟预期(在客户端测量为20到30毫秒)。但在两次交换之间,蓝色部分需要86毫秒。究竟是怎么回事?在数据包333中,我们的服务向/latest/meta-data/iam/security-credentials发送一个HTTPGET请求,然后在同一TCP连接/arn:..上发送到/latest/meta-data/iam/security-credentials另一个GET请求。我们验证并发现整个流程中的每个请求都会发生这种情况。在容器中,DNS解析确实有点慢(原因也很有意思,有机会我会在另一篇文章中详细讨论)。但是,高延迟的真正原因是针对每个单独请求的AWS实例元数据服务查询。假设#2:对AWS的恶意调用的两个端点都是AWS实例元数据API的一部分。当从ElasticSearch读取信息时,我们的微服务将使用此服务。这两个调用是授权工作方式的基本流程,端点通过第一个请求生成与实例关联的IAM角色。/#curlhttp://169.254.169.254/latest/meta-data/iam/security-credentials/arn:aws:iam::
