{"id":"IL-0174","org":"Hyperconnect","date":"2023-05-02","docs":["https://docs.fluentbit.io/manual/about/fluentd-and-fluent-bit","https://github.com/fluent/fluent-bit/blob/v2.0.3/plugins/out_kafka/kafka.c#L193","https://www.gnu.org/savannah-checkouts/gnu/gdb/index.html","https://docs.fluentbit.io/manual/concepts/key-concepts#event-or-record","https://github.com/fluent/fluent-bit/blob/v2.0.3/plugins/out_es/es.c#L376-L384","https://github.com/fluent/fluent-bit/pull/6452"],"title":{"en":"After an upgrade to Fluent Bit 2.0, logging sidecars that used the Kafka output plugin kept restarting with SIGSEGV because the plugin read a corrupted record as a map without checking its type","ko":"Fluent Bit 2.0 으로 올린 로그 수집 사이드카 가운데 Kafka output 플러그인을 쓰는 컨테이너가 SIGSEGV 로 계속 재시작됐다."},"domain":["kubernetes","observability"],"impact":{"scope":"Fluent Bit sidecar containers using the Kafka output plugin, on a service chosen for low user impact","users":"unknown","region":"unknown"},"review":{"written_by":"session","reviewed_at":null,"reviewed_by":null},"lessons":{"en":"A segmentation fault in a container can be debugged from its core file, but Kubernetes does not keep one by default: a volume mounted into the pod and a core_pattern pointing into it on the node are needed first. The debug image of the same version, run locally on the same CPU architecture with the matching source checkout, gives a gdb environment that matches production.\n\nCore files contain part of the process memory and may hold sensitive data, so they should be shared with care.","ko":"Linux 는 segmentation fault 가 나면 core 를 덤프하고, 이 파일을 분석하면 버그를 찾을 수 있다. Kubernetes 환경에서는 core 가 저절로 남지 않으므로 볼륨과 core_pattern 을 미리 잡아 두어야 한다. 직접 분석하기 어려워 core 파일을 다른 사람에게 넘길 때는 조심해야 한다. core 에는 실행 중이던 프로세스의 메모리 일부가 들어 있어 민감 정보가 섞일 수 있다."},"related":[],"sources":[{"url":"https://hyperconnect.github.io/2023/05/02/troubleshooting-fluent-bit-segmentation-fault.html","kind":"blog","license":"unknown","archived":null}],"summary":{"en":"Hyperconnect runs Fluent Bit as its log collector in Kubernetes, mostly as a DaemonSet and as a pod sidecar for applications that write especially many logs, feeding an EFK stack. After an upgrade to Fluent Bit 2.0, sidecars using the Kafka output plugin crashed repeatedly with a segmentation fault.\n\nCore dump analysis showed that some event chunks arrived corrupted: after a valid record the buffer was filled with null bytes, and unpacking it produced objects that were not the expected array of timestamp and map. The Kafka output plugin passed whatever came back to produce_message(), which treated it as a map and walked memory with a bogus size until it crashed. The other output plugins already skipped records that were not maps; the Kafka plugin did not. The author could not find where the corruption came from, because event chunks are processed asynchronously and the core held no stack frame for that point; it was presumably introduced by the input_tail_file and filter_modify plugins in the configured pipeline.\n\nThe fix was to make the Kafka output plugin ignore non-map data before calling produce_message(). The problem did not reproduce reliably with the same input, so no test was written, but a custom build of the patched plugin running as the sidecar stopped the segmentation faults. The change was submitted upstream as fluent-bit pull request 6452 and merged eight days later.","ko":"하이퍼커넥트는 Kubernetes 클러스터에서 Fluent Bit 를 로그 수집 에이전트로 쓰고, ElasticSearch, Kibana 와 함께 EFK 스택을 꾸려 두었다. 보통 DaemonSet 으로 띄우지만 로그를 특히 많이 내는 애플리케이션에는 파드 사이드카로 붙인다. Fluent Bit 2.0 이 나오자 사용자 영향이 적은 서비스부터 사이드카를 올렸는데, Kafka output 플러그인을 쓰는 컨테이너만 SIGSEGV 로 재시작됐다.\n\n팀은 core 파일을 얻으려고 파드에 hostPath 볼륨을 붙이고, 노드에서 커널 파라미터 core_pattern 을 /tmp/cores/core.%e.%p.%h.%t 로 바꿨다. 다음 날 core 파일을 확보했고, 같은 arm64 환경을 fluent/fluent-bit:2.0.3-debug 이미지로 로컬에 띄운 뒤 v2.0.3 소스를 받아 gdb 로 분석했다.\n\n분석해 보니 produce_message 함수가 map 이어야 할 인자를 타입 확인 없이 map 으로 읽다가 segmentation fault 를 냈다. 이 함수를 부른 cb_kafka_flush 는 msgpack_unpack_next 로 꺼낸 레코드를 flb_time_pop_from_msgpack 에 넘겼는데, 이 레코드가 array 가 아니라 MSGPACK_OBJECT_POSITIVE_INTEGER 였다. event_chunk 데이터를 덤프해 Python msgpack 으로 풀어 보니 정상 메시지 뒤에 null 바이트가 대량으로 붙어 있었다. event_chunk 는 비동기로 처리돼 core 에 그때의 스택 프레임이 없었고, 그래서 데이터가 왜 깨졌는지는 끝내 찾지 못했다. 팀은 설정으로 보아 input_tail_file 과 filter_modify 플러그인이 데이터를 만든 시점부터 깨졌을 것으로 추정했다. 다른 output 플러그인들은 이 경우를 예외로 처리하고 있었고, Kafka output 플러그인에만 이 처리가 빠져 있었다.\n\n같은 입력을 줘도 항상 재현되지는 않아 테스트 코드는 쓰지 못했다. 대신 map 이 아닌 데이터가 produce_message 에 들어가지 않도록 고친 커스텀 빌드를 사이드카로 적용해 segmentation fault 가 사라지는 것을 확인했다. 그리고 업스트림에 PR 을 냈다. PR 은 제출 8일 만에 병합됐다."},"trigger":{"en":"Upgrading the Fluent Bit logging sidecar to version 2.0 (the 2.0.3 image) on a service with low user impact.","ko":"운영 클러스터에서 Fluent Bit 사이드카를 2.0 으로 업그레이드했다. 이 버전의 Kafka output 플러그인은 map 이 아닌 레코드가 들어와도 타입을 확인하지 않고 처리했다."},"severity":"unknown","symptoms":["crash-loop"],"timeline":{"detected_after":null,"resolved_after":null},"date_kind":"published","detection":{"en":"The team monitored the upgraded sidecars for several days after the upgrade and saw that the containers using the Kafka output plugin were restarting with SIGSEGV.","ko":"업그레이드 뒤 며칠 동안 모니터링하다가 Kafka output 플러그인을 쓰는 컨테이너만 SIGSEGV 로 재시작되는 것을 발견했다."},"components":["telemetry-agent"],"root_cause":"software-bug","remediation":[{"text":{"en":"patched the Fluent Bit Kafka output plugin to skip data that is not a map before calling produce_message()","ko":"produce_message 를 부르기 전에 map 이 아닌 데이터를 건너뛰도록 Fluent Bit Kafka output 플러그인을 고쳤다."}},{"text":{"en":"ran a custom build with the patch as the sidecar, which stopped the segmentation faults","ko":"고친 플러그인으로 커스텀 빌드를 만들어 사이드카로 적용했고, segmentation fault 가 사라졌다."}},{"text":{"en":"submitted the patch upstream as fluent-bit pull request 6452, which was merged eight days later","ko":"수정 사항을 Fluent Bit 업스트림에 PR 6452 로 냈고, 제출 8일 만에 병합됐다."}}],"action_items":[],"contributing":["limited-debug-access"],"recurrence_of":null,"symptoms_text":{"en":"Hyperconnect upgraded the Fluent Bit sidecar of one service with little user impact to Fluent Bit 2.0 and watched it for several days. Only the containers that used the Kafka output plugin were being restarted, each time on SIGSEGV. The stack trace printed at the crash pointed into the Kafka output plugin, but no core file was left behind, because Fluent Bit ran inside Kubernetes.\n\nTo capture one, the team mounted a hostPath volume into the pod and, on the node running the sidecar, set the kernel parameter core_pattern to \"/tmp/cores/core.%e.%p.%h.%t\". A test with a sleep process killed by SIGSEGV confirmed the dump appeared on both the pod and the host, and by the next morning a core named core.flb-out-kafka.0.1.some-pod-7dd6dc88c7-rz724.1669206388 had been written.\n\nOpened with gdb in the fluent/fluent-bit:2.0.3-debug image (arm64), with the v2.0.3 source checked out, the backtrace showed the crash in produce_message() at plugins/out_kafka/kafka.c:189, \"key = map->via.map.ptr[i].key;\", called from cb_kafka_flush(). The variable map had type MSGPACK_OBJECT_FLOAT64 with the value 1669206388.2559929, a timestamp, but the code read it through the union as a map with size 1561354800, and the loop counter i had reached 165717.\n\nOne frame up, the unpacked result had type MSGPACK_OBJECT_POSITIVE_INTEGER, so flb_time_pop_from_msgpack had received input that did not match the event structure. The event chunk was 4911 bytes and the failure was at offset 859; from that offset on the buffer held only null bytes. Decoding the dumped chunk with Python msgpack showed one valid record followed by a long run of zeros.","ko":"하이퍼커넥트는 운영 Kubernetes 클러스터의 로그 수집 에이전트인 Fluent Bit 를 2.0 으로 올렸다. 사용자 영향이 적은 서비스를 골라 Fluent Bit 사이드카부터 업그레이드하고 며칠 지켜보니, Kafka output 플러그인을 쓰는 컨테이너만 SIGSEGV 로 재시작되고 있었다.\n\n크래시 때 함께 찍힌 스택트레이스를 보면 out_kafka 플러그인의 kafka.c 에서 크래시가 났다. 정확한 원인을 보려면 core 파일이 필요했지만, Fluent Bit 가 Kubernetes 환경에서 돌고 있어서 core 파일이 남지 않았다.\n\ncore 를 확보해 gdb 로 열어 보니 bt 결과의 #3 프레임이 produce_message 였고, kafka.c:189 의 key = map->via.map.ptr[i].key 에서 segmentation fault 가 났다. map 변수의 타입은 MSGPACK_OBJECT_FLOAT64 였는데 코드는 이를 map 으로 읽고 있었다. 그래서 map->via.map 의 size 가 1561354800 이라는 터무니없이 큰 값으로 나왔다. event_chunk 의 데이터 크기는 4,911 바이트였고, 오프셋 859 부터는 전부 null 바이트였다."},"updated_at":"2026-11-09T11:55:55.708576Z","url":"https://benchlet.com/incidentlib/app/cases/IL-0174","org_slug":"hyperconnect"}