《云原生网络数据面可观测性最佳实践》——五、 典型问题华山论剑——1 某客户nginx ingress偶发性出现4xx or 5xx(上)

简介: 《云原生网络数据面可观测性最佳实践》——五、 典型问题华山论剑——1 某客户nginx ingress偶发性出现4xx or 5xx(上)

问题背景

客户反馈在某时间段连续分钟级别出现5xx


排查过程

查看ingressPod 日志其中status :499, request_time: 5.000,  upstream_response_time:4.999根据这几个点说明问题发生在了ingress 转发到后端pod这一段客户端未在5s内收到body,主动断开了连接

此时怀疑两个方面:后端应用负载太高,发生了限流或应用本身队列等溢出了查看了cgroup监控,pod并未触发限流,所以需要从其他方面入手

可以依照 4.3.2小节 《Nginx Ingress 499/502/503/504相关问题场景配置相关net-exporter采集指标

image.png

image.png

image.png

问题时间点,passiveopens有一个补偿态变化,先降低后上升,但是变化区间不大,可以认为下游外部访问流量没有特别大变化

问题时间点,activeopens有明显突增,tcptimeouts和synretrans都有明显增长,结合sock分配数变化,此时应该有ingress侧大量建立连接重试动作

 

综上推测此时upstream与ingress之间存在一些问题,导致ingress访问upstream出现连接建立失败并批量重试的功能。

image.png

 查看ingress 后端SVC的pod的net-exporter指标listendrops和listenoverflow均大于0,并且幅度增长较大,可以明确的是存在连接队列溢出的问题。

 

根因原理

TCP协议作为一个有状态的协议,对于服务端来说,提供应用层完成一次标准的报文交互需要经历几个过程,在这期间,需要依赖不同类型的sock数据结构完成不同的任务,按照顺序依次如下:



创建一个socket,并且调用bind()和listen()系统调用开始监听这个端口上服务,此时创建了一个inet_connection_sock,inet_connection_sock保存着建立连接所需要一些信息以及连接队列

当有合法syn报文被提交给inet_connection_sock进行处理时,就进入了TCP三次握手状态机:

    服务端第一次收到syn报文后,会创建一个request_sock(真实分配了sock内存),存放少量基础信息,这个request_sock会被放入inet_connection_sockicsk_accept_queue队列中

   服务端第收到第三次握手合法报文时,会查找icsk_accept_queue中request_sock,如果有合法request_sock,则会完成一系列操作,分配一个代表已经处于TCP_ESTABLISHEDsock



看到上面的分析,大概就能知道,我们所说的“队列溢出”,指的是用于listen的socket的用于存放request_sock的队列和tcp_sock的队列产生了溢出。

 

看到上面的分析,大概就能知道,我们所说的“队列溢出”,指的是用于listen的socket的用于存放request_sock的队列和tcp_sock的队列产生了溢出。在不同版本的内核中,这两个”队列“起步并不总是以队列的形式存在,以我们alinux内核4.19版本的代码为例,内核在判断这两处的溢出时的核心代码逻辑如下:

 

request_sock的队列,request_sock是一个轻量级的用于记录一个连接回话信息的sock类型(mini sock to represent a connection request),对于一个inet_connection_sock来说,会专门分配一个成员队列来存放所有处于连接状态的request_sock,即icsk_accept_queue,也就是俗称的"半连接队列",所以判断这个队列是否会溢出,需要对icsk_accept_queue的长度进行判断:

static inline void inet_csk_reqsk_queue_added(struct sock *sk)
{
  reqsk_queue_added(&inet_csk(sk)->icsk_accept_queue);
}
static inline int inet_csk_reqsk_queue_len(const struct sock *sk)
{
  return reqsk_queue_len(&inet_csk(sk)->icsk_accept_queue);
}
static inline int inet_csk_reqsk_queue_is_full(const struct sock *sk)
{
  return inet_csk_reqsk_queue_len(sk) >= sk->sk_max_ack_backlog;
}

尽管在不同内核版本中有实现上差异,但是对于request_sock队列,内核采用inet_csk_reqsk_queue_is_full进行判断,判断方式就是获取request_sock队列长度并且与sock中存放sk_max_ack_backlog进行比对那么什么时候会进行这个检查呢?

 

int tcp_conn_request(struct request_sock_ops *rsk_ops,
         const struct tcp_request_sock_ops *af_ops,
         struct sock *sk, struct sk_buff *skb)
{
  /* TW buckets are converted to open requests without
   * limitations, they conserve resources and peer is
   * evidently real one.
   */
  if ((net->ipv4.sysctl_tcp_syncookies == 2 ||
       inet_csk_reqsk_queue_is_full(sk)) && !isn) {
    want_cookie = tcp_syn_flood_action(sk, skb, rsk_ops->slab_name);
    if (!want_cookie)
      goto drop;
  }
  if (sk_acceptq_is_full(sk)) {
    NET_INC_STATS(sock_net(sk), LINUX_MIB_LISTENOVERFLOWS);
    goto drop;
  }
  if (!want_cookie && !isn) {
    /* Kill the following clause, if you dislike this way. */
    if (!net->ipv4.sysctl_tcp_syncookies &&
        (net->ipv4.sysctl_max_syn_backlog - inet_csk_reqsk_queue_len(sk) <
         (net->ipv4.sysctl_max_syn_backlog >> 2)) &&
        !tcp_peer_is_proven(req, dst)) {
      pr_drop_req(req, ntohs(tcp_hdr(skb)->source),
            rsk_ops->family);
      goto drop_and_release;
    }
    isn = af_ops->init_seq(skb);
  }
drop_and_release:
  dst_release(dst);
drop_and_free:
  reqsk_free(req);
drop:
  tcp_listendrop(sk);
  return 0;

 内核在tcp_conn_request中进行了inet_csk_reqsk_queue_is_full检查,也就是当LISTEN状态sock收到了一个合法syn报文时所谓“半连接队列溢出”,也就是syn队列溢出,其实就是还处在握手阶段未就绪连接过多导致,这里逻辑在不同版本内核上有着比较明显变化,尤其是在容器时代到来,许多sysctl都成为了net namespace级别后

 

ESTABLISHED状态tcpsock队列溢出,通常特被称之为accept队列或者俗称“全连接队列”,在内核中,使用以下方法用于判断溢出现象是否发生:

static inline void sk_acceptq_removed(struct sock *sk)
{
  sk->sk_ack_backlog--;
}
static inline void sk_acceptq_added(struct sock *sk)
{
  sk->sk_ack_backlog++;
}
static inline bool sk_acceptq_is_full(const struct sock *sk)
{
  return sk->sk_ack_backlog > sk->sk_max_ack_backlog;
}

 可以看到,尽管也被称为“队列”,但是在内核的实际行为中,其实并没有真正存在一个队列,而是改为通过sk_ack_backlog计数来进行判断。那么内核在什么情况下会对已经完成连接建立的socket进行溢出的判断呢?在上面request_sock的溢出判断中,其实已经存在了一个,在第一次收到SYN报文是,如果这个LISTEN的地址已经有了足够多的就绪的连接,那么就会选择丢弃掉这个还未完全建立的连接,除此之外,还有一处也会进行判断:

/*
 * The three way handshake has completed - we got a valid synack -
 * now create the new socket.
 */
struct sock *tcp_v4_syn_recv_sock(const struct sock *sk, struct sk_buff *skb,
          struct request_sock *req,
          struct dst_entry *dst,
          struct request_sock *req_unhash,
          bool *own_req)
{
  if (sk_acceptq_is_full(sk))
    goto exit_overflow;
  newsk = tcp_create_openreq_child(sk, req, skb);
  if (!newsk)
    goto exit_nonewsk;
  *own_req = inet_ehash_nolisten(newsk, req_to_sk(req_unhash));
  if (likely(*own_req)) {
    tcp_move_syn(newtp, req);
    ireq->ireq_opt = NULL;
  } else {
    newinet->inet_opt = NULL;
  }
  return newsk;
exit_overflow:
  NET_INC_STATS(sock_net(sk), LINUX_MIB_LISTENOVERFLOWS);
exit_nonewsk:
  dst_release(dst);
exit:
  tcp_listendrop(sk);
  return NULL;
put_and_exit:
  newinet->inet_opt = NULL;
  inet_csk_prepare_forced_close(newsk);
  tcp_done(newsk);
  goto exit;

从代码的名称不难发现,tcp_v4_syn_recv_sock正是内核处理第三次握手的ACK报文的核心逻辑,这里通过sk_acceptq_is_full判断了是否当前已经就绪的连接数量已经超过了设定值,如果没有超过,则会通过核心的inet_ehash_nolisten增加一个新的ESTABLISHED状态的tcpsock,后续就不会由LISTEN状态的inet_connection_sock来处理报文了。



回答这一小节最初的问题,连接队列溢出的直接原因是什么呢?

 

连接队列溢出可以分request_sock队列溢出(syn-queue/半连接队列)和就绪连接(accept-queue/全连接队列)超出限制,其中前者指的是还在握手过程中,完成了第一次握手但是没有完成第三次握手的会话过多,后者则是单纯的单个LISTEN地址的就绪连接太多了。

 

在了解了原因之后,我们再看一下如何站在直接的原因的基础上去解决这个问题。从上面原因的分析来看,对于客户正常的业务遇到的问题,排除SYN-Flooding等攻击行为的干扰,解决问题的核心,其实是让单个LISTEN地址可以容纳更多的就绪连接,从上文中可以发现,真正核心的代码其实是如下一行:

sk->sk_ack_backlog > sk->sk_max_ack_backlog;


更多精彩内容,欢迎观看:

《云原生网络数据面可观测性最佳实践》——五、 典型问题华山论剑——1 某客户nginx ingress偶发性出现4xx or 5xx(下):https://developer.aliyun.com/article/1221272?groupCode=supportservice

相关实践学习
通过Ingress进行灰度发布
本场景您将运行一个简单的应用,部署一个新的应用用于新的发布,并通过Ingress能力实现灰度发布。
容器应用与集群管理
欢迎来到《容器应用与集群管理》课程,本课程是“云原生容器Clouder认证“系列中的第二阶段。课程将向您介绍与容器集群相关的概念和技术,这些概念和技术可以帮助您了解阿里云容器服务ACK/ACK Serverless的使用。同时,本课程也会向您介绍可以采取的工具、方法和可操作步骤,以帮助您了解如何基于容器服务ACK Serverless构建和管理企业级应用。 学习完本课程后,您将能够: 掌握容器集群、容器编排的基本概念 掌握Kubernetes的基础概念及核心思想 掌握阿里云容器服务ACK/ACK Serverless概念及使用方法 基于容器服务ACK Serverless搭建和管理企业级网站应用
相关文章
|
3月前
|
人工智能 安全 Cloud Native
阿里云云原生安全能力全线升级,护航百万客户云上安全
【重磅发布】9月20日,在杭州云栖大会上,阿里云宣布云原生安全能力全线升级,首次发布云原生网络检测与响应产品NDR(Network Detection Response,简称NDR)。同时,阿里云还宣布将持续增加免费的安全防护能力,帮助中小企业客户以极低投入完成基础的云上安全风险治理。
198 15
|
1月前
|
Kubernetes Cloud Native Ubuntu
庆祝 .NET 9 正式版发布与 Dapr 从 CNCF 毕业:构建高效云原生应用的最佳实践
2024年11月13日,.NET 9 正式版发布,Dapr 从 CNCF 毕业,标志着云原生技术的成熟。本文介绍如何使用 .NET 9 Aspire、Dapr 1.14.4、Kubernetes 1.31.0/Containerd 1.7.14、Ubuntu Server 24.04 LTS 和 Podman 5.3.0-rc3 构建高效、可靠的云原生应用。涵盖环境准备、应用开发、Dapr 集成、容器化和 Kubernetes 部署等内容。
57 5
|
2月前
|
人工智能 Cloud Native 安全
从云原生到 AI 原生,网关的发展趋势和最佳实践
本文整理自阿里云智能集团资深技术专家,云原生产品线中间件负责人谢吉宝(唐三)在云栖大会的精彩分享。讲师深入浅出的分享了软件架构演进过程中,网关所扮演的各类角色,AI 应用的流量新特征对软件架构和网关所提出的新诉求,以及基于阿里自身实践所带来的开源贡献和商业能力。
261 11
|
2月前
|
监控 Cloud Native 持续交付
云原生架构下微服务的最佳实践与挑战####
【10月更文挑战第20天】 本文深入探讨了云原生架构在现代软件开发中的应用,特别是针对微服务设计模式的最优实践与面临的主要挑战。通过分析容器化、持续集成/持续部署(CI/CD)、服务网格等关键技术,阐述了如何高效构建、部署及运维微服务系统。同时,文章也指出了在云原生转型过程中常见的难题,如服务间的复杂通信、安全性问题以及监控与可观测性的实现,为开发者和企业提供了宝贵的策略指导和解决方案建议。 ####
52 5
|
1月前
|
Kubernetes Cloud Native 持续交付
云原生架构下的微服务设计原则与最佳实践##
在数字化转型的浪潮中,云原生技术以其高效、灵活和可扩展的特性成为企业IT架构转型的首选。本文深入探讨了云原生架构的核心理念,聚焦于微服务设计的关键原则与实施策略,旨在为开发者提供一套系统性的方法论,以应对复杂多变的业务需求和技术挑战。通过分析真实案例,揭示了如何有效利用容器化、持续集成/持续部署(CI/CD)、服务网格等关键技术,构建高性能、易维护的云原生应用。文章还强调了文化与组织变革在云原生转型过程中的重要性,为企业顺利过渡到云原生时代提供了宝贵的见解。 ##
|
2月前
|
存储 运维 监控
云原生应用的可观察性:理解、实现与最佳实践
【10月更文挑战第10天】随着云原生技术的发展,可观察性成为确保应用性能和稳定性的重要因素。本文探讨了云原生应用可观察性的概念、实现方法及最佳实践,包括监控、日志记录和分布式追踪的核心组件,以及如何通过选择合适的工具和策略来提升应用的可观察性。
|
3月前
|
Cloud Native 关系型数据库 Serverless
基于阿里云函数计算(FC)x 云原生 API 网关构建生产级别 LLM Chat 应用方案最佳实践
本文带大家了解一下如何使用阿里云Serverless计算产品函数计算构建生产级别的LLM Chat应用。该最佳实践会指导大家基于开源WebChat组件LobeChat和阿里云函数计算(FC)构建企业生产级别LLM Chat应用。实现同一个WebChat中既可以支持自定义的Agent,也支持基于Ollama部署的开源模型场景。
691 28
|
7月前
|
运维 Kubernetes Cloud Native
构建高效云原生运维体系:Kubernetes最佳实践
【5月更文挑战第9天】 在动态和快速演变的云计算环境中,高效的运维是确保应用稳定性与性能的关键。本文将深入探讨在Kubernetes环境下,如何通过一系列最佳实践来构建一个高效且响应灵敏的云原生运维体系。文章不仅涵盖了容器化技术的选择与优化、自动化部署、持续集成/持续交付(CI/CD)流程的整合,还讨论了监控、日志管理以及灾难恢复策略的重要性。这些实践旨在帮助运维团队有效应对微服务架构下的复杂性,确保系统可靠性及业务的连续性。
|
6月前
|
弹性计算 监控 Cloud Native
构建多模态模型,生成主机观测指标,欢迎来战丨2024天池云原生编程挑战赛
本次比赛旨在如何通过分析 ECS 性能数据和任务信息,综合利用深度学习、序列分析等先进技术,生成特定机器的性能指标。参赛者的解决方案将为云资源管理和优化决策提供重要参考,助力云计算资源的高效稳定运行和智能化调度。
668 19
|
5月前
|
弹性计算 运维 安全
面对蓝屏,阿里云云原生能力可以帮客户做点啥?
Windows大面积蓝屏,问题源于“CSAgent.sys”加载错误设定的“C-00000291*.sys”文件。阿里云充分利用云原生能力,通过ECS实例自助排查和OOS批量操作快速修复受损机器。