Kubernetes 클러스터의 네트워크 대기 시간 문제를 디버깅하는 방법에 대한 클라우드 네이티브 심층 분석

I. 소개

  • Kubernetes 클러스터의 규모가 지속적으로 증가함에 따라 서비스 대기 시간에 대한 요구 사항이 점점 더 엄격해지고 있습니다. 때로는 Kubernetes 플랫폼에서 실행되는 일부 서비스에서 가끔 대기 시간 문제에 직면하는 경우가 관찰됩니다. 이러한 간헐적인 문제는 성능 문제로 인한 것이 아닙니다. 응용 프로그램 자체가 발생했습니다.
  • Kubernetes 클러스터의 애플리케이션으로 인해 발생하는 지연 문제가 무작위적인 것처럼 보인다는 것을 점차 알게 되었습니다. 일부 네트워크 연결 설정에는 100ms 이상이 소요되어 다운스트림 서비스가 시간 초과되거나 재시도될 수 있습니다. 이러한 서비스 자체가 비즈니스 응답을 처리합니다. 시간은 100ms 이내에는 잘 유지되지만 연결을 설정하는 데 100ms 이상이 걸리므로 참을 수 없습니다. 또한 매우 빠르게(밀리초 단위) 실행되어야 하는 일부 SQL 쿼리의 경우 애플리케이션 관점에서는 실제로 100ms를 초과하지만, MySQL 데이터베이스 관점에서는 완전히 정상적이며 느려질 가능성이 없습니다. 쿼리 문제가 발견되었습니다.
  • 문제 해결을 통해 클러스터 내부 요청이나 외부 리소스 및 외부 방문자와 관련된 요청을 포함하여 Kubernetes 노드와의 연결 설정 링크로 문제의 범위를 좁힐 수 있습니다. 이 문제를 재현하는 가장 간단한 방법은 내부 노드에서 Vegeta를 사용하여 NodePort로 노출된 서비스에 대해 HTTP 스트레스 테스트를 실행하는 것입니다. 대기 시간이 긴 일부 요청이 때때로 생성되는 것을 볼 수 있습니다. 그렇다면 이 문제를 어떻게 추적하고 찾을 수 있을까요?

2. 문제 분석

  • 간단한 예를 들어 문제를 재현하려고 하면 문제의 범위를 좁히고 불필요한 복잡성을 제거할 수 있기를 바랍니다. 처음에는 Vegeta와 Kubernetes Pod 사이의 데이터 흐름에 관련된 구성 요소가 너무 많았고 이것이 더 깊은 네트워크 문제인지 판단하기 어려웠으므로 빼기가 필요했습니다.

여기에 이미지 설명을 삽입하세요.

  • Vegeta 클라이언트는 클러스터의 Kube 노드에 대한 TCP 요청을 시작합니다. 데이터 센터의 Kubernetes 클러스터는 오버레이 네트워크(기존 데이터 센터 네트워크에서 실행)를 사용하고 데이터 센터의 오버레이 네트워크의 IP 패킷을 캡슐화합니다. IP 패킷 내에서. 요청이 첫 번째 kube 노드에 도달하면 NAT 변환을 수행하여 kube 노드의 IP와 포트를 오버레이의 네트워크 주소, 특히 애플리케이션을 실행하는 Pod의 IP와 포트로 변환합니다. 응답을 요청하면 해당 역변환(SNAT/DNAT)이 발생합니다. 이는 서비스가 배포됨에 따라 지속적으로 업데이트되는 많은 양의 변경 가능한 상태를 유지하는 매우 복잡한 시스템입니다.
  • 스트레스 테스트를 위해 Vegeta를 처음 사용했을 때 TCP 핸드셰이크 단계(SYN과 SYN-ACK 사이)에 지연이 있음을 발견했습니다. HTTP와 Vegeta로 인해 발생하는 복잡성을 단순화하기 위해 hping3를 사용하여 SYN 패킷을 보내고 응답 패킷이 지연되는지 관찰한 다음 연결을 닫고 100ms를 초과하는 지연이 있는 패킷을 필터링하여 Vegeta를 간단히 재현합니다. SYN 공격에 노출되는 서비스를 테스트하거나 시뮬레이션합니다. 다음 로그는 TCP SYN/SYN-ACK 패킷을 10ms 간격으로 kube-node의 포트 30927로 보내고 느린 요청을 필터링한 결과를 보여줍니다.
theojulienne@shell ~ $ sudo hping3 172.16.47.27 -S -p 30927 -i u10000 | egrep --line-buffered 'rtt=[0-9]{
    
    3}\.'
len=46 ip=172.16.47.27 ttl=59 DF id=0 sport=30927 flags=SA seq=1485 win=29200 rtt=127.1 ms
len=46 ip=172.16.47.27 ttl=59 DF id=0 sport=30927 flags=SA seq=1486 win=29200 rtt=117.0 ms
len=46 ip=172.16.47.27 ttl=59 DF id=0 sport=30927 flags=SA seq=1487 win=29200 rtt=106.2 ms
len=46 ip=172.16.47.27 ttl=59 DF id=0 sport=30927 flags=SA seq=1488 win=29200 rtt=104.1 ms
len=46 ip=172.16.47.27 ttl=59 DF id=0 sport=30927 flags=SA seq=5024 win=29200 rtt=109.2 ms
len=46 ip=172.16.47.27 ttl=59 DF id=0 sport=30927 flags=SA seq=5231 win=29200 rtt=109.2 ms
  • 로그의 시퀀스 번호와 시간을 보면 가장 먼저 관찰할 수 있는 점은 이러한 지연이 단일 사건이 아니라 마치 요청의 백로그가 최종적으로 한 번에 처리되는 것처럼 클러스터에서 자주 발생한다는 점입니다.
  • 다음으로, 예외가 발생할 수 있는 구성 요소를 구체적으로 찾아보겠습니다. 수백 줄 길이이므로 kube-proxy에 대한 NAT 규칙인가요? 아니면 IPIP 터널이나 유사한 네트워크 구성 요소의 성능이 좋지 않습니까? 문제를 해결하는 한 가지 방법은 시스템의 모든 단계를 테스트하는 것입니다. NAT 규칙과 방화벽 논리를 제거하고 IPIP 터널만 사용하면 어떻게 되나요?

여기에 이미지 설명을 삽입하세요.

  • Kube 노드에도 있는 경우 Linux는 매우 간단하게 Pod와의 직접 통신을 허용합니다.
theojulienne@kube-node-client ~ $ sudo hping3 10.125.20.64 -S -i u10000 | egrep --line-buffered 'rtt=[0-9]{
    
    3}\.'
len=40 ip=10.125.20.64 ttl=64 DF id=0 sport=0 flags=RA seq=7346 win=0 rtt=127.3 ms
len=40 ip=10.125.20.64 ttl=64 DF id=0 sport=0 flags=RA seq=7347 win=0 rtt=117.3 ms
len=40 ip=10.125.20.64 ttl=64 DF id=0 sport=0 flags=RA seq=7348 win=0 rtt=107.2 ms
  • 결과에서 볼 수 있듯이 iptables 및 NAT의 문제를 배제한 문제가 여전히 존재합니다. TCP에 문제가 있습니까? ICMP 요청을 사용하면 어떤 일이 발생하는지 살펴보겠습니다.
theojulienne@kube-node-client ~ $ sudo hping3 10.125.20.64 --icmp -i u10000 | egrep --line-buffered 'rtt=[0-9]{
    
    3}\.'
len=28 ip=10.125.20.64 ttl=64 id=42594 icmp_seq=104 rtt=110.0 ms
len=28 ip=10.125.20.64 ttl=64 id=49448 icmp_seq=4022 rtt=141.3 ms
len=28 ip=10.125.20.64 ttl=64 id=49449 icmp_seq=4023 rtt=131.3 ms
len=28 ip=10.125.20.64 ttl=64 id=49450 icmp_seq=4024 rtt=121.2 ms
len=28 ip=10.125.20.64 ttl=64 id=49451 icmp_seq=4025 rtt=111.2 ms
len=28 ip=10.125.20.64 ttl=64 id=49452 icmp_seq=4026 rtt=101.1 ms
len=28 ip=10.125.20.64 ttl=64 id=50023 icmp_seq=4343 rtt=126.8 ms
len=28 ip=10.125.20.64 ttl=64 id=50024 icmp_seq=4344 rtt=116.8 ms
len=28 ip=10.125.20.64 ttl=64 id=50025 icmp_seq=4345 rtt=106.8 ms
len=28 ip=10.125.20.64 ttl=64 id=59727 icmp_seq=9836 rtt=106.1 ms
  • 결과는 ICMP가 여전히 문제를 재현할 수 있음을 보여줍니다. IPIP 터널이 문제의 원인입니까? 문제를 더욱 단순화해 보겠습니다.

여기에 이미지 설명을 삽입하세요.

  • 그러면 이러한 노드 간의 통신으로 인해 이 문제가 발생할 가능성이 있습니까?
theojulienne@kube-node-client ~ $ sudo hping3 172.16.47.27 --icmp -i u10000 | egrep --line-buffered 'rtt=[0-9]{
    
    3}\.'
len=46 ip=172.16.47.27 ttl=61 id=41127 icmp_seq=12564 rtt=140.9 ms
len=46 ip=172.16.47.27 ttl=61 id=41128 icmp_seq=12565 rtt=130.9 ms
len=46 ip=172.16.47.27 ttl=61 id=41129 icmp_seq=12566 rtt=120.8 ms
len=46 ip=172.16.47.27 ttl=61 id=41130 icmp_seq=12567 rtt=110.8 ms
len=46 ip=172.16.47.27 ttl=61 id=41131 icmp_seq=12568 rtt=100.7 ms
len=46 ip=172.16.47.27 ttl=61 id=9062 icmp_seq=31443 rtt=134.2 ms
len=46 ip=172.16.47.27 ttl=61 id=9063 icmp_seq=31444 rtt=124.2 ms
len=46 ip=172.16.47.27 ttl=61 id=9064 icmp_seq=31445 rtt=114.2 ms
len=46 ip=172.16.47.27 ttl=61 id=9065 icmp_seq=31446 rtt=104.2 ms
  • 이러한 복잡성 뒤에는 간단히 말해서 ICMP를 포함한 두 kube 노드 간의 네트워크 통신이 있습니다. 이 대상 노드가 "비정상"인 경우(일부 노드는 대기 시간이 길고 문제가 자주 발생하는 등 다른 노드보다 더 나쁨) 문제가 발생할 때 여전히 비슷한 지연이 나타납니다.
  • 이제 질문은 분명히 이 문제가 모든 머신에서 발견되지 않는데 왜 이 문제가 kube 노드의 서버에만 나타나는가 하는 것입니다. kube 노드가 요청 발신자 또는 요청 수신자 역할을 할 때 발생합니까? 다행스럽게도 이제는 문제의 범위를 쉽게 좁힐 수 있습니다. 클러스터 외부의 머신을 발신자로 사용할 수 있고 동일한 "알려진 오류" 머신을 요청 대상으로 사용할 수 있지만 이 요청에서 방향 문제가 여전히 존재합니다.
theojulienne@shell ~ $ sudo hping3 172.16.47.27 -p 9876 -S -i u10000 | egrep --line-buffered 'rtt=[0-9]{
    
    3}\.'
len=46 ip=172.16.47.27 ttl=61 DF id=0 sport=9876 flags=RA seq=312 win=0 rtt=108.5 ms
len=46 ip=172.16.47.27 ttl=61 DF id=0 sport=9876 flags=RA seq=5903 win=0 rtt=119.4 ms
len=46 ip=172.16.47.27 ttl=61 DF id=0 sport=9876 flags=RA seq=6227 win=0 rtt=139.9 ms
len=46 ip=172.16.47.27 ttl=61 DF id=0 sport=9876 flags=RA seq=7929 win=0 rtt=131.2 ms
  • 그런 다음 위 작업을 반복합니다. 이번에는 kube 노드에서 외부 노드로 요청을 보냅니다.
theojulienne@kube-node-client ~ $ sudo hping3 172.16.33.44 -p 9876 -S -i u10000 | egrep --line-buffered 'rtt=[0-9]{
    
    3}\.'
^C
--- 172.16.33.44 hping statistic ---
22352 packets transmitted, 22350 packets received, 1% packet loss
round-trip min/avg/max = 0.2/7.6/1010.6 ms
  • 패킷 캡처의 지연 데이터를 보면 더 많은 정보를 얻을 수 있습니다. 특히, 전송 측(아래)에서는 대기 시간이 관찰되지만 수신 서버에서는 대기 시간(위)이 표시되지 않습니다. 그림의 델타 열(초)을 참고하세요.

여기에 이미지 설명을 삽입하세요.

  • 또한, 수신단에서 TCP와 ICMP 네트워크 패킷의 순서(시퀀스 ID 기준)의 차이를 살펴보면 ICMP 패킷은 항상 보낸 순서대로 수신단에 도착하지만, 전송 시간이 불규칙하고 TCP 패킷의 시퀀스 ID가 때때로 엇갈리게 되며 일부가 일시 중지됩니다. 특히, SYN 패킷을 주고받는 포트를 세어 보면, 이 포트들은 수신 측에서는 순차적이지 않고 송신 측에서는 순차적입니다.
  • 자체 데이터 센터에서 사용되는 네트워크 카드와 같이 현재 서버에서 사용되는 네트워크 카드는 TCP 및 ICMP 네트워크 메시지를 처리하는 데 약간의 차이가 있습니다. 데이터그램이 도착하면 네트워크 카드는 각 연결에 전달된 패킷을 해시하고 서로 다른 수신 대기열에 서로 다른 연결을 할당하려고 시도하며 (아마도) 각 대기열에 CPU 코어를 할당합니다. TCP 패킷의 경우 이 해시 값에는 소스 IP와 포트, 대상 IP와 포트가 모두 포함됩니다. 즉, 각 연결의 해시 값이 다를 가능성이 높습니다. ICMP 패킷의 경우 포트가 없기 때문에 해시 값에는 소스 IP와 대상 IP만 포함되며, 이는 위의 결과를 설명합니다.
  • 또 다른 새로운 발견은 두 호스트 사이의 ICMP 패킷이 일정 기간 동안 정체된 반면 TCP 패킷은 같은 기간 동안 괜찮았다는 것입니다. 이는 수신 네트워크 카드 큐의 해시가 "농담"임을 알려주는 것으로 보이며, 이는 송신 측의 문제라기보다는 수신 측에서 RX 패킷을 처리하는 동안 일시 중지가 발생한 것이 거의 확실합니다. 이는 kube 노드 간의 전송 문제를 배제하므로 이제 이는 패킷 처리 단계의 지연이고 수신 측에 있는 일부 kube 노드라는 것이 알려져 있습니다.

3. 리눅스 커널의 네트워크 패킷 처리 과정

  • kube node 서비스 수신단에서 문제가 발생하는 이유를 이해하기 위해 Linux가 네트워크 패킷을 처리하는 방법을 살펴보겠습니다. 가장 간단하고 원시적인 구현에서는 네트워크 패킷을 수신한 후 네트워크 카드가 Linux 커널에 인터럽트를 보내 네트워크 패킷을 처리해야 함을 알립니다. 커널은 현재 수행 중인 다른 작업을 중지하고 컨텍스트를 인터럽트 처리기로 전환하고 네트워크 패킷을 처리한 다음 이전 작업 작업으로 다시 전환합니다.

여기에 이미지 설명을 삽입하세요.

  • 컨텍스트 전환은 매우 느립니다. 이 방법은 1990년대 10Mbit 네트워크 카드에서는 문제가 되지 않았지만 현재 많은 서버에 10G 네트워크 카드가 있으며 최대 패킷 처리 속도는 초당 1,500만 패킷에 도달할 수 있습니다. 소형 8코어 서버에서 이는 초당 수백만 개의 인터럽트를 의미합니다.
  • 수년 전 Linux에는 새로운 NAPI가 추가되었습니다. 네트워킹 API는 과거의 전통적인 방법을 대체하는 데 사용되었으며 최신 네트워크 카드 드라이버는 이 새로운 API를 사용하여 고속 패킷 처리 성능을 크게 향상시킵니다. 낮은 속도에서도 커널은 앞서 설명한 대로 네트워크 카드로부터의 인터럽트를 계속 받아들입니다. 임계값을 초과하는 패킷이 도착하면 커널은 인터럽트를 비활성화하고 네트워크 카드 폴링을 시작하여 네트워크 패킷을 일괄적으로 캡처합니다. 이 프로세스는 "softirq"에서 완료되거나 소프트웨어 인터럽트 컨텍스트라고도 합니다. 이는 프로그램이 사용자 공간 대신 ​​커널 공간에 들어갈 때 시스템 호출이 끝날 때 발생합니다.

여기에 이미지 설명을 삽입하세요.

  • 이 방법은 기존 방법보다 속도가 훨씬 빠르지만 또 다른 문제도 발생합니다. 패킷 수가 너무 많아서 모든 CPU 시간이 네트워크 카드에서 수신된 패킷을 처리하는 데 소비되지만 이로 인해 사용자 모드 프로그램이 실제로 대기열(예: TCP 연결에서 오는 네트워크 요청)을 처리할 수 없는 경우 데이터 가져오기 등), 결국 대기열이 가득 차고 패킷이 삭제되기 시작합니다. 사용자 모드와 커널 모드 사이의 실행 시간 균형을 맞추기 위해 커널은 주어진 소프트웨어 인터럽트 컨텍스트에 대해 처리 패킷 수를 제한하고 "예산"을 마련합니다. 이 "예산" 값이 초과되면 "ksoftiqrd"(또는 ps 명령에서 이 스레드를 볼 수 있음)라는 다른 스레드를 깨워 일반 시스템 호출 경로 외부에서 소프트웨어를 계속 처리합니다. 인터럽트 컨텍스트, 이 스레드는 표준 프로세스 스케줄러를 사용하여 공정한 스케줄링을 달성합니다.

여기에 이미지 설명을 삽입하세요.

  • 네트워크 패킷을 처리하기 위해 Linux 커널이 사용하는 경로를 정렬하면 이 처리 프로세스가 실제로 일시 중지될 수 있음을 알 수 있습니다. Softirq 처리 호출 간의 간격이 길어지면 네트워크 패킷이 일정 시간 동안 네트워크 카드의 RX 대기열에 있을 수 있습니다. 이는 CPU 코어 교착 상태 또는 커널이 Softirq를 처리하지 못하도록 차단하는 일부 느린 처리 ​​작업 때문일 수 있습니다. ...

4. 문제를 특정 핵심이나 방법으로 좁힙니다.

  • 지금까지 우리는 이러한 지연이 실제로 가능하다고 믿고 있으며 매우 유사한 징후가 관찰되는 것 같습니다. 다음 단계는 이론을 확인하고 문제의 원인을 이해하려고 노력하는 것입니다.
  • 문제를 일으킨 네트워크 요청을 살펴보겠습니다.
len=46 ip=172.16.53.32 ttl=61 id=29573 icmp_seq=1953 rtt=99.3 ms
len=46 ip=172.16.53.32 ttl=61 id=29574 icmp_seq=1954 rtt=89.3 ms
len=46 ip=172.16.53.32 ttl=61 id=29575 icmp_seq=1955 rtt=79.2 ms
len=46 ip=172.16.53.32 ttl=61 id=29576 icmp_seq=1956 rtt=69.1 ms
len=46 ip=172.16.53.32 ttl=61 id=29577 icmp_seq=1957 rtt=59.1 ms
len=46 ip=172.16.53.32 ttl=61 id=29790 icmp_seq=2070 rtt=75.7 ms
len=46 ip=172.16.53.32 ttl=61 id=29791 icmp_seq=2071 rtt=65.6 ms
len=46 ip=172.16.53.32 ttl=61 id=29792 icmp_seq=2072 rtt=55.5 ms
  • 앞에서 설명한 것처럼 이러한 ICMP 패킷은 특정 네트워크 카드 RX 대기열로 해시된 다음 특정 CPU 코어에서 처리됩니다. 커널이 수행하는 작업을 이해하려면 먼저 해당 커널이 어떤 CPU 코어인지, Softirq 및 ksoftiqrd가 이러한 패킷을 어떻게 처리하는지 알아야 합니다. 이는 문제를 찾는 데 매우 도움이 됩니다.
  • Linux 커널의 실행 상태를 실시간으로 추적하는 데 사용할 수 있는 도구가 있는데, 이를 위해 bcc를 사용할 수 있습니다. bcc를 사용하면 작은 C 프로그램을 작성하고 이를 커널의 모든 기능에 마운트할 수 있습니다. 그런 다음 이벤트를 캐시하고 이를 사용자 모드 Python 프로그램으로 전송할 수 있으며, 이 Python 프로그램은 이러한 이벤트에 대한 요약 분석을 수행한 다음 결과를 보여줍니다. 귀하에게 반환됩니다. 위에서 언급한 "커널 내 임의의 기능에 마운트"는 사실 어려운 점이지만, 이러한 프로덕션 환경의 문제를 추적하도록 설계되었기 때문에 최대한 안전하게 사용하였습니다. 일반적으로 문제는 테스트나 개발에서 쉽게 재현되지 않습니다. 환경.
  • 커널이 IMCP Ping 패킷을 처리하고 있다는 것을 알고 있으므로 커널의 icmp_echo 메소드를 가로채겠습니다. 이 메소드는 인바운드 방향 ICMP "에코 요청" 패킷을 승인하고 ICMP 응답 "에코 응답"을 시작합니다. 이러한 패킷은 다음으로 식별됩니다. hping3에 표시된 icmp_seq 시퀀스 번호. 이 bcc 스크립트의 코드는 복잡해 보일 수 있지만, 분석해 보면 그렇게 무섭게 들리지는 않습니다. icmp_echo 함수에는 ICMP 에코 요청이 포함된 데이터 패킷인 sk_buff * skb 구조에 대한 포인터가 전달됩니다. 좀 더 파고들어 echo.sequence(위의 hping3에 표시된 icmp_seq에 해당)를 추출하고 이를 사용자 공간으로 다시 보낼 수 있습니다. 동시에 현재 프로세스 이름이나 프로세스 ID를 쉽게 얻을 수도 있습니다.
  • 커널이 이러한 패킷을 처리하면 다음 결과를 볼 수 있습니다.
TGID    PID     PROCESS NAME    ICMP_SEQ
0       0       swapper/11      770
0       0       swapper/11      771
0       0       swapper/11      772
0       0       swapper/11      773
0       0       swapper/11      774
20041   20086   prometheus      775
0       0       swapper/11      776
0       0       swapper/11      777
0       0       swapper/11      778
4512    4542   spokes-report-s  779
  • 여기서 프로세스 이름에 대해 주목해야 할 점은 시스템 호출 이후에 발생하는 Softirq의 컨텍스트에서 이 시스템 호출을 시작한 프로세스가 이를 처리하는 커널임에도 불구하고 "프로세스"로 표시되는 것을 볼 수 있다는 것입니다. 커널의 맥락에서.
  • 실행을 통해 이제 hping3에서 관찰한 정지된 패킷을 이를 처리한 프로세스와 연관시키는 것이 가능합니다. 캡처된 icmp_seq 값에 대한 간단한 grep은 이러한 패킷이 처리되기 전에 어떤 일이 발생했는지 확인할 수 있는 컨텍스트를 제공합니다. 위의 hping3에 표시된 icmp_seq 값과 일치하는 패킷이 표시되었으며 관찰된 rtt 값도 표시됩니다(괄호 안에는 RTT<50ms인 요청이 필터링되지 않는다고 가정함).
TGID    PID     PROCESS NAME    ICMP_SEQ ** RTT
--
10137   10436   cadvisor        1951
10137   10436   cadvisor        1952
76      76      ksoftirqd/11    1953 ** 99ms
76      76      ksoftirqd/11    1954 ** 89ms
76      76      ksoftirqd/11    1955 ** 79ms
76      76      ksoftirqd/11    1956 ** 69ms
76      76      ksoftirqd/11    1957 ** 59ms
76      76      ksoftirqd/11    1958 ** (49ms)
76      76      ksoftirqd/11    1959 ** (39ms)
76      76      ksoftirqd/11    1960 ** (29ms)
76      76      ksoftirqd/11    1961 ** (19ms)
76      76      ksoftirqd/11    1962 ** (9ms)
--
10137   10436   cadvisor        2068
10137   10436   cadvisor        2069
76      76      ksoftirqd/11    2070 ** 75ms
76      76      ksoftirqd/11    2071 ** 65ms
76      76      ksoftirqd/11    2072 ** 55ms
76      76      ksoftirqd/11    2073 ** (45ms)
76      76      ksoftirqd/11    2074 ** (35ms)
76      76      ksoftirqd/11    2075 ** (25ms)
76      76      ksoftirqd/11    2076 ** (15ms)
76      76      ksoftirqd/11    2077 ** (5ms)
  • 위의 결과에서 먼저 이러한 패킷은 ksoftirqd/11 프로세스에 의해 처리되며, 이는 이 특정 시스템이 ICMP 패킷을 수신기의 CPU 코어 11에 해시했음을 편리하게 알려줍니다. 지연되면 cadvisor의 시스템 호출 Softirq의 컨텍스트에서 일부 패킷이 처리되는 것을 항상 볼 수 있으며 ksoftirqd가 백로그를 인계받아 처리하며 이는 우리가 발견한 지연된 패킷에 해당합니다.
  • cAdvisor가 항상 요청이 중단되기 직전에 실행된다는 사실은 이것이 우리가 해결하고 있는 문제와 관련이 있을 수 있음을 시사합니다. 아이러니하게도 cAdvisor 홈페이지에 설명된 대로 "실행 중인 컨테이너의 리소스 사용량 및 성능 특성을 정확하게 분석"하기 위해 cAdvisor를 정확하게 사용함으로써 이러한 성능 문제가 발생했습니다. 컨테이너와 관련된 많은 것들이 그렇듯이, 이것들은 상대적으로 최첨단 도구이며 예상치 못한 성능 저하를 일으키는 상황이 있습니다.

5. cAdvisor는 일시 정지를 유발하기 위해 어떤 조치를 취했습니까?

  • 이제 지연이 발생하는 방식, 이를 유발하는 프로세스 및 이를 유발하는 CPU 코어를 이해했으므로 이제 이에 대해 잘 이해하게 되었습니다. 커널이 ksoftirqd를 미리 예약하는 대신 하드 차단하고 cAdvisor의 Softirq 컨텍스트에서 처리되는 패킷을 확인하기 위해 cAdvisor가 syscall을 호출하는 속도가 매우 느릴 수 있으며 네트워크의 나머지 부분을 완료한 후 패킷은 정상적으로 처리될 수 있습니다:

여기에 이미지 설명을 삽입하세요.

  • 이것은 단지 이론일 뿐인데, 이것이 실제로 일어나고 있는지 어떻게 확인합니까? 우리가 할 수 있는 일은 프로세스 전반에 걸쳐 CPU 코어에서 실행 중인 항목을 추적하고, 패킷이 "예산"을 초과하는 지점을 찾아 ksoftirqd 처리를 깨우기 시작한 다음 돌아가서 CPU 코어에서 실행 중인 항목을 확인하는 것입니다. 몇 밀리초마다 CPU의 X-ray를 촬영하는 것과 같다고 생각하십시오. 다음과 같습니다.

여기에 이미지 설명을 삽입하세요.

  • 심층 탐색은 이 요구 사항의 대부분이 구현되었다는 것입니다. 성능 기록 도구는 특정 빈도로 지정된 CPU 코어를 샘플링할 수 있으며 실시간 호출 그래프(사용자 공간 및 커널 포함)를 생성할 수 있습니다. Brendan Gregg가 개발한 FlameGraph에서 파생된 프로그램을 사용하여 실시간 호출을 기록하고 조작할 수 있습니다. 이 도구는 스택 추적의 순서를 유지하고 1ms마다 스택 추적의 한 줄을 샘플링한 다음 ksoftirqd 이전 100ms의 샘플을 얻을 수 있습니다. 실행됩니다. :
# record 999 times a second, or every 1ms with some offset so not to align exactly with timers
sudo perf record -C 11 -g -F 999
# take that recording and make a simpler stack trace.
sudo perf script 2>/dev/null | ./FlameGraph/stackcollapse-perf-ordered.pl | grep ksoftir -B 100
  • 결과는 다음과 같습니다. (비슷해 보이는 수백 개의 흔적)
cadvisor;[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];entry_SYSCALL_64_after_swapgs;do_syscall_64;sys_read;vfs_read;seq_read;memcg_stat_show;mem_cgroup_nr_lru_pages;mem_cgroup_node_nr_lru_pages
cadvisor;[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];entry_SYSCALL_64_after_swapgs;do_syscall_64;sys_read;vfs_read;seq_read;memcg_stat_show;mem_cgroup_nr_lru_pages;mem_cgroup_node_nr_lru_pages
cadvisor;[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];entry_SYSCALL_64_after_swapgs;do_syscall_64;sys_read;vfs_read;seq_read;memcg_stat_show;mem_cgroup_iter
cadvisor;[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];entry_SYSCALL_64_after_swapgs;do_syscall_64;sys_read;vfs_read;seq_read;memcg_stat_show;mem_cgroup_nr_lru_pages;mem_cgroup_node_nr_lru_pages
cadvisor;[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];entry_SYSCALL_64_after_swapgs;do_syscall_64;sys_read;vfs_read;seq_read;memcg_stat_show;mem_cgroup_nr_lru_pages;mem_cgroup_node_nr_lru_pages
ksoftirqd/11;ret_from_fork;kthread;kthread;smpboot_thread_fn;smpboot_thread_fn;run_ksoftirqd;__do_softirq;net_rx_action;ixgbe_poll;ixgbe_clean_rx_irq;napi_gro_receive;netif_receive_skb_internal;inet_gro_receive;bond_handle_frame;__netif_receive_skb_core;ip_rcv_finish;ip_rcv;ip_forward_finish;ip_forward;ip_finish_output;nf_iterate;ip_output;ip_finish_output2;__dev_queue_xmit;dev_hard_start_xmit;ipip_tunnel_xmit;ip_tunnel_xmit;iptunnel_xmit;ip_local_out;dst_output;__ip_local_out;nf_hook_slow;nf_iterate;nf_conntrack_in;generic_packet;ipt_do_table;set_match_v4;ip_set_test;hash_net4_kadt;ixgbe_xmit_frame_ring;swiotlb_dma_mapping_error;hash_net4_test
ksoftirqd/11;ret_from_fork;kthread;kthread;smpboot_thread_fn;smpboot_thread_fn;run_ksoftirqd;__do_softirq;net_rx_action;gro_cell_poll;napi_gro_receive;netif_receive_skb_internal;inet_gro_receive;__netif_receive_skb_core;ip_rcv_finish;ip_rcv;ip_forward_finish;ip_forward;ip_finish_output;nf_iterate;ip_output;ip_finish_output2;__dev_queue_xmit;dev_hard_start_xmit;dev_queue_xmit_nit;packet_rcv;tpacket_rcv;sch_direct_xmit;validate_xmit_skb_list;validate_xmit_skb;netif_skb_features;ixgbe_xmit_frame_ring;swiotlb_dma_mapping_error;__dev_queue_xmit;dev_hard_start_xmit;__bpf_prog_run;__bpf_prog_r
  • 로그는 많지만 주의 깊게 살펴보면 cAdvisor와 ksoftirqd라는 고정된 패턴을 발견했을 수 있습니다. 그렇다면 이는 무엇을 의미합니까? 각 줄은 특정 순간의 추적 기록이며 호출된 각 메서드 스택의 메서드는 세미콜론으로 구분됩니다. 줄 중간에서 호출된 syscall이 read()인 것을 볼 수 있습니다. ...;dosyscall_64;sys_read;... 따라서 cAdvisor는 mem_cgroup과 관련된 read() 시스템 호출을 호출하는 데 많은 시간을 소비합니다. * 함수는 메소드이기 때문에 스택의 맨 아래에 있는 메소드입니다. 메서드 스택 추적은 특정 읽기 내용을 쉽게 표시할 수 없으므로 strace를 사용하여 cAdvisor가 수행하는 작업을 확인하고 100ms 이상 걸리는 시스템 호출을 찾을 수 있습니다.
theojulienne@kube-node-bad ~ $ sudo strace -p 10137 -T -ff 2>&1 | egrep '<0\.[1-9]'
[pid 10436] <... futex resumed> )       = 0 <0.156784>
[pid 10432] <... futex resumed> )       = 0 <0.258285>
[pid 10137] <... futex resumed> )       = 0 <0.678382>
[pid 10384] <... futex resumed> )       = 0 <0.762328>
[pid 10436] <... read resumed> "cache 154234880\nrss 507904\nrss_h"..., 4096) = 658 <0.179438>
[pid 10384] <... futex resumed> )       = 0 <0.104614>
[pid 10436] <... futex resumed> )       = 0 <0.175936>
[pid 10436] <... read resumed> "cache 0\nrss 0\nrss_huge 0\nmapped_"..., 4096) = 577 <0.228091>
[pid 10427] <... read resumed> "cache 0\nrss 0\nrss_huge 0\nmapped_"..., 4096) = 577 <0.207334>
[pid 10411] <... epoll_ctl resumed> )   = 0 <0.118113>
[pid 10382] <... pselect6 resumed> )    = 0 (Timeout) <0.117717>
[pid 10436] <... read resumed> "cache 154234880\nrss 507904\nrss_h"..., 4096) = 660 <0.159891>
[pid 10417] <... futex resumed> )       = 0 <0.917495>
[pid 10436] <... futex resumed> )       = 0 <0.208172>
[pid 10417] <... futex resumed> )       = 0 <0.190763>
[pid 10417] <... read resumed> "cache 0\nrss 0\nrss_huge 0\nmapped_"..., 4096) = 576 <0.154442>
  • 이 시점에서 read() 시스템 호출이 매우 느리다는 것을 확신할 수 있습니다. read로 읽은 내용과 mem_cgroup의 컨텍스트로 판단하면 해당 read() 호출은 시스템의 메모리 사용량과 cgroup 제한을 설명하는 데 사용되는 memory.state 파일을 읽는 것입니다. cAdvisor는 이 파일을 폴링하여 컨테이너에서 사용하는 리소스의 세부 정보를 얻고, 이 메서드를 수동으로 호출하여 문제가 커널에 있는지 cAdvisor에 있는지 확인합니다.
theojulienne@kube-node-bad ~ $ time cat /sys/fs/cgroup/memory/memory.stat >/dev/null

real    0m0.153s
user    0m0.000s
sys    0m0.152s
theojulienne@kube-node-bad ~ $
  • 이 문제는 재현 가능하므로 커널에 의해 유발되는 "병리학적" 방법임을 시사합니다.

6. 읽는 속도가 왜 그렇게 느린가요?

  • 이 단계에서는 다른 사람들이 보고한 유사한 문제를 쉽게 찾을 수 있습니다. 알고 보니 이 문제는 이미 cAdvisor에 보고되었으며, 이는 높은 CPU 사용량 문제로 밝혀졌을 뿐, 지연 시간이 네트워크 스택에도 무작위로 영향을 미치고 있다는 사실은 발견되지 않았습니다. 실제로 일부 내부 개발자들은 cAdvisor가 예상보다 많은 CPU를 소모하고 있다는 점을 인지하고 있으나, 저희 서버는 CPU 성능이 넉넉하여 CPU 사용량에 대한 조사는 이루어지지 않았기 때문에 문제가 되지 않는 것으로 보입니다.
  • 이 질문을 보면 주로 네임스페이스(컨테이너) 내에서 메모리 사용량을 관리하고 계산하는 역할을 담당하는 메모리 cgroup에 관한 것입니다. 메모리 cgroup은 cgroup의 모든 프로세스가 종료될 때 Docker에 의해 해제됩니다. 그러나 "메모리"는 프로세스의 메모리일 뿐만 아니라 프로세스 메모리의 사용량이 없어지는 동안 커널은 덴트리 및 아이노드(디렉토리 및 파일 메타데이터)와 같은 캐시 공간에도 메모리를 할당하는 것으로 밝혀졌습니다. 메모리 cgroup에 캐시됩니다. 이 질문에서 볼 수 있듯이: "좀비" cgroups: 실행 중인 프로세스가 없고 삭제된 cgroup은 여전히 ​​일정한 양의 메모리 공간을 보유합니다(우리의 경우 이러한 캐시 객체는 디렉토리 데이터이지만 페이지일 수도 있습니다). 캐시 또는 tmpfs).
  • cgroup이 릴리스될 때 모든 캐시 페이지를 순회하는 대신(또한 매우 느릴 수 있음) 커널은 메모리를 회수하기 전에 메모리가 사용될 때까지 천천히 기다립니다. 모든 메모리 페이지가 지워지면 해당 cgroup이 최종적으로 재활용됩니다. 동시에 이러한 cgroup은 여전히 ​​통계에 포함됩니다.
  • 성능 관점에서 각 페이지를 분할하여 재활용하고 초기 정리를 빠르게 수행하도록 선택하여 직접 블록 재활용에 소요되는 막대한 시간을 상각하지만 이렇게 하면 일부 캐시가 메모리에 유지됩니다. 하지만 괜찮습니다. 커널이 캐시에서 마지막 메모리 페이지를 회수하면 cgroup이 결국 정리되므로 이는 "누수"가 아닙니다. 불행히도 문제는 memory.stat가 검색을 수행하는 방식에 있습니다. 예를 들어 일부 서버에서는 커널이 여전히 버전 4.9입니다. 이 버전의 구현에 문제가 있습니다. 게다가 우리 서버는 일반적으로 많은 메모리 공간을 가지고 있습니다. 이는 좀비 cgroup의 마지막 메모리 캐시 회수 및 정리에 오랜 시간이 걸릴 수 있음을 의미합니다.
  • 노드에는 다수의 좀비 cgroup이 있고 일부는 1초 이상 읽기/일시 중지가 있었던 것으로 나타났습니다. 이 cAdvisor 문제에 대한 임시 해결 방법은 시스템 전체 디렉터리/inode 캐시를 즉시 해제하는 것입니다. 이렇게 하면 읽기 대기 시간이 즉시 제거되고 네트워크 대기 시간도 해결됩니다. 캐시 제거에는 "좀비" cgroup 캐시 페이지가 차지한 페이지도 포함되기 때문입니다. 동시에. 이는 최종 해결 방법은 아니지만 문제의 원인을 확인하는 것입니다.
  • 최신 커널 버전(4.19+)에서는 memory.stat 호출 성능이 향상되므로 이 커널 버전으로 업데이트한 후에는 더 이상 문제가 되지 않습니다. 그동안 기존 도구를 사용하여 Kubernetes 클러스터의 노드 문제를 감지하고 노드를 정상적으로 제거하고 다시 시작합니다. 이러한 도구는 문제를 유발할 만큼 높은 대기 시간 상황을 감지하는 데 사용됩니다. 그런 다음 정상적인 방법으로 처리됩니다. 재시작. 이는 나머지 서버의 시스템과 커널을 업그레이드할 수 있는 숨쉴 공간을 제공했습니다.

7. 요약

  • 이 문제는 NIC RX 대기열이 수백 밀리초 동안 정지하는 것으로 나타나기 때문에 짧은 연결에서 높은 대기 시간은 물론 연결 중간(예: MySQL 쿼리와 응답 패킷 간)에도 지연이 발생합니다. Kubernetes와 같은 가장 기본적인 시스템의 성능을 이해하고 유지하는 것은 그 위에 구축된 모든 서비스의 안정성과 속도에 매우 중요합니다.

추천

출처blog.csdn.net/Forever_wj/article/details/134948470