Kafka ZooKeeper 연결 문제 심층 분석
구성, 네트워크, 타임아웃, 로그, 브로커 부하에 대한 실용적인 점검을 통해 Kafka ZooKeeper 연결 실패를 진단합니다.
Kafka ZooKeeper 연결 문제 심층 분석
Kafka ZooKeeper 연결 문제는 주로 오래된 Kafka 클러스터와 KRaft 모드로 전환하지 않은 클러스터에 영향을 미칩니다. 최신 Kafka 배포는 ZooKeeper 없이 실행될 수 있지만, 많은 프로덕션 시스템이 여전히 ZooKeeper에 의존하고 있습니다. 브로커가 server.properties에서 zookeeper.connect를 사용한다면 ZooKeeper는 여전히 제어 플레인의 일부이며 Kafka 자체만큼 신경을 써야 합니다.
Kafka 브로커가 ZooKeeper 세션을 유지하지 못하면 증상이 단순한 연결 문제보다 더 심각해 보일 수 있습니다. 브로커가 재시작될 수 있습니다. 컨트롤러 선출이 반복될 수 있습니다. 파티션을 사용할 수 없게 될 수 있습니다. 로그에는 세션 만료, 컨트롤러 사임 또는 반복적인 재연결 시도가 표시될 수 있습니다. 프로듀서와 컨슈머는 메타데이터 오류, 타임아웃 또는 불안정한 리더와 같은 다운스트림 효과만 볼 수 있습니다.
ZooKeeper의 역할부터 시작하세요. ZooKeeper 기반 Kafka 클러스터에서 브로커는 ZooKeeper에 자신을 등록하고, 컨트롤러 선거는 ZooKeeper에 의존하며, 클러스터 메타데이터 조정은 ZooKeeper를 통해 이루어집니다. 브로커가 ZooKeeper 세션을 잃어 세션이 만료될 정도로 오래 지속되면 Kafka는 해당 브로커가 클러스터에서 사라진 것으로 간주합니다. 브로커 프로세스가 계속 실행 중이더라도 클러스터는 리더십을 해당 브로커에서 이동시킬 수 있습니다.
첫 번째 점검은 지루하지만 종종 문제를 찾아냅니다: 모든 브로커에서 zookeeper.connect를 확인하세요.
zookeeper.connect=zk01.example.com:2181,zk02.example.com:2181,zk03.example.com:2181/kafka
zookeeper.connection.timeout.ms=18000
zookeeper.session.timeout.ms=18000
연결 문자열은 Kafka가 도달할 수 있는 앙상블 멤버를 나열해야 합니다. /kafka와 같은 chroot 경로를 사용하는 경우 모든 브로커에 일관되게 포함시키세요. 절반의 브로커는 /kafka로 구성하고 나머지 절반은 그렇지 않게 구성하지 마십시오. 그들은 서로 다른 Kafka 클러스터와 통신하는 것처럼 동작할 것입니다. chroot를 사용하는 경우 먼저 생성하거나 ZooKeeper 도구를 사용하여 존재하는지 확인하세요.
구성 텍스트뿐만 아니라 DNS도 확인하세요. 랩톱에서 올바르게 확인되는 호스트 이름이 브로커 서브넷에서는 실패할 수 있습니다. Kafka 브로커 호스트에서 점검을 실행하세요. 배스천 호스트가 동일한 네트워크 경로를 가지고 있지 않다면 배스천 호스트에서 실행하지 마세요.
getent hosts zk01.example.com
nc -vz zk01.example.com 2181
nc -vz zk02.example.com 2181
nc -vz zk03.example.com 2181
성공적인 TCP 연결이 ZooKeeper가 정상임을 증명하지는 않지만, 실패한 연결은 방화벽, 보안 그룹, 라우팅, DNS 또는 리스너 구성을 계속 파헤칠 충분한 이유가 됩니다. 모든 Kafka 브로커에서 모든 ZooKeeper 노드로 테스트하세요. 부분적인 연결은 완전한 장애보다 더 나쁩니다. 브로커가 특정 앙상블 멤버에 연결을 시도할 때만 실패가 나타날 수 있기 때문입니다.
ZooKeeper의 네 글자 명령어는 활성화된 경우 도움이 될 수 있습니다. 많은 설치에서 이를 제한하므로 작동한다고 가정하지 마세요. 허용되는 경우 ruok은 imok을 반환해야 하며 mntr은 유용한 서버 통계를 표시할 수 있습니다.
echo ruok | nc zk01.example.com 2181
echo mntr | nc zk01.example.com 2181
이러한 명령어가 비활성화된 경우 지원되는 관리 도구나 모니터링 스택을 대신 사용하세요. 요점은 간단한 질문에 답하는 것입니다: ZooKeeper가 수신 대기 중이고, 앙상블에 참여하고 있으며, 빠르게 응답하고 있습니까?
다음으로 ZooKeeper 앙상블 상태를 검사하세요. 3노드 앙상블은 하나의 ZooKeeper 노드가 다운되는 것을 견딜 수 있습니다. 두 개는 견딜 수 없습니다. 5노드 앙상블은 두 개를 견딜 수 있습니다. 짝수 크기의 앙상블은 피하세요. 예상과 달리 쿼럼을 개선하지 못하면서 비용만 추가하기 때문입니다. 3과 5가 일반적인 선택입니다.
ZooKeeper 쪽에서는 zoo.cfg를 살펴보세요. clientPort, tickTime, initLimit, syncLimit 및 서버 라인을 확인하세요. 광고된 서버 호스트 이름이 Kafka 브로커뿐만 아니라 ZooKeeper 노드 간에 도달 가능한지 확인하세요. ZooKeeper 피어는 자체 쿼럼 및 리더 선거 포트가 필요합니다. Kafka 브로커는 2181에 도달할 수 있지만 ZooKeeper 앙상블 자체는 피어 트래픽이 차단되어 비정상일 수 있습니다.
세션 타임아웃 튜닝은 또 다른 일반적인 혼란의 원인입니다. Kafka는 ZooKeeper에 세션 타임아웃을 요청하지만 ZooKeeper는 자체 구성에 따라 제한을 적용합니다. ZooKeeper에서 최소 세션 타임아웃은 일반적으로 2 * tickTime이고 최대값은 일반적으로 20 * tickTime입니다(특정 서버 설정으로 재정의되지 않은 경우). 즉, 허용 범위를 벗어난 Kafka 타임아웃 값은 ZooKeeper에 의해 조정될 수 있습니다.
tickTime=2000인 경우 일반적인 허용 세션 범위는 대략 4초에서 40초입니다. zookeeper.session.timeout.ms=18000과 같은 Kafka 설정은 해당 범위 내에 있습니다. 매우 낮은 타임아웃은 짧은 네트워크 일시 중지 또는 가비지 수집 일시 중지 동안 잘못된 실패를 생성할 수 있습니다. 매우 높은 타임아웃은 실제 브로커 실패를 감지하는 데 더 오래 걸리게 할 수 있습니다. 민감도와 안정성 사이에서 선택하는 것입니다.
tickTime을 함부로 변경하지 마세요. Kafka뿐만 아니라 ZooKeeper 앙상블에도 영향을 미칩니다. 브로커 일시 중지에 대한 더 많은 허용 오차가 필요한 경우 ZooKeeper 타이밍을 변경하기 전에 Kafka의 zookeeper.session.timeout.ms, 브로커 JVM 동작 및 네트워크 상태를 검토하는 것이 더 나은 경우가 많습니다.
로그는 일반적으로 타임스탬프별로 정렬하면 이야기를 알려줍니다. Kafka 브로커에서 ZooKeeper 연결 끊김 및 세션 만료에 대한 메시지를 검색하세요:
rg -i "zookeeper|session|expired|controller|reconnect" /var/log/kafka/server.log
패턴이 단일 라인보다 더 중요합니다. 계획된 ZooKeeper 재시작 중 한 번의 재연결은 무해할 수 있습니다. 몇 분마다 반복되는 만료는 불안정성을 가리킵니다. 가비지 수집 중 만료는 JVM 일시 중지 또는 브로커 과부하를 가리킵니다. 많은 브로커에서 동시에 만료가 발생하면 ZooKeeper, 네트워크 또는 공유 인프라 이벤트를 가리킵니다.
ZooKeeper 노드에서 리더 변경, fsync 경고, 연결 제한 및 긴 요청 대기 시간을 확인하세요. ZooKeeper는 트랜잭션 로그를 쓰기 때문에 디스크 대기 시간에 민감합니다. 느린 디스크는 서비스가 연결 가능해 보이지만 안정적인 세션에 충분히 빠르게 응답하지 못하게 할 수 있습니다.
ZooKeeper의 경우 원시 대역폭보다 네트워크 대기 시간과 패킷 손실이 더 중요합니다. Kafka 브로커는 ZooKeeper에 엄청난 처리량이 필요하지 않지만 안정적이고 지연 시간이 짧은 통신이 필요합니다. 브로커와 ZooKeeper가 멀리 떨어진 네트워크로 분할된 경우 문제가 발생할 것으로 예상하세요. 가까이 두세요. 클라우드 환경에서는 불필요한 NAT, 과부하된 방화벽 또는 교차 리전 경로를 통해 브로커-투-ZooKeeper 트래픽을 라우팅하지 마세요.
Kafka 브로커의 리소스 경합은 정확히 ZooKeeper 문제처럼 보일 수 있습니다. JVM이 긴 가비지 수집 일시 중지 동안 세계를 멈추면 브로커가 하트비트를 놓칠 수 있습니다. CPU가 포화 상태이면 하트비트 처리가 지연될 수 있습니다. 호스트가 높은 I/O 대기 상태에 갇히면 Kafka가 조정 작업을 따라잡지 못할 수 있습니다. ZooKeeper 연결 끊김과 동일한 타임스탬프에서 브로커 메트릭을 확인하세요.
유용한 브로커 측 질문은 다음과 같습니다: 연결 끊김 전에 힙 사용량이 증가했습니까? GC 일시 중지 시간이 급증했습니까? 디스크 I/O 대기가 높았습니까? 네트워크 재전송이 증가했습니까? 동시에 대규모 파티션 재할당 또는 리더 이동이 있었습니까? 부하에 허덕이는 브로커는 더 적은 파티션 리더, 더 나은 디스크, JVM 튜닝 또는 트래픽 이동이 필요할 수 있습니다. ZooKeeper 타임아웃을 늘리면 원인을 해결하지 않고 증상을 숨길 수 있습니다.
구성 일관성은 간과하기 쉽습니다. 동일한 Kafka 클러스터의 모든 Kafka 브로커는 동일한 ZooKeeper 연결 문자열과 chroot를 사용해야 합니다. 또한 고유한 broker.id 값을 가져야 합니다. 중복된 브로커 ID는 두 프로세스가 동일한 브로커를 나타내려고 하기 때문에 혼란스러운 등록 동작을 유발할 수 있습니다.
최근에 ZooKeeper 호스트 이름, 인증서, 방화벽 규칙 또는 Kafka 브로커 구성을 변경한 경우 작동하는 브로커와 실패하는 브로커를 비교하세요. 작은 차이가 일반적입니다: 오래된 DNS 접미사, 누락된 chroot 경로, 두 브로커에는 연결되었지만 세 번째에는 연결되지 않은 보안 그룹 또는 하나의 systemd 환경 파일의 오타.
복구는 무엇이 고장났는지에 따라 다릅니다. 방화벽 규칙이 누락된 경우 수정하고 브로커가 깔끔하게 다시 연결되지 않으면 영향을 받는 브로커를 다시 시작하세요. ZooKeeper가 쿼럼을 잃은 경우 Kafka 브로커를 재시작하기 전에 먼저 쿼럼을 복원하세요. 브로커가 과부하로 인해 만료된 경우 다시 시작하면 일시적으로 다시 작동할 수 있지만 압력을 제거하지 않으면 문제가 다시 나타납니다.
롤링 재시작을 사용하세요. ZooKeeper가 불안정했기 때문에 모든 Kafka 브로커를 한 번에 다시 시작하면 부분적인 장애가 전체 장애로 바뀔 수 있습니다. ZooKeeper 상태를 복원한 다음 컨트롤러 안정성과 파티션 리더십을 주시하면서 브로커를 한 번에 하나씩 다시 시작하거나 복구하세요.
장기적인 안정성을 위해 양쪽을 모니터링하세요. ZooKeeper에서는 요청 대기 시간, 미해결 요청, 리더 변경, 팔로워 동기화 상태, 디스크 공간 및 프로세스 재시작을 관찰하세요. Kafka에서는 컨트롤러 변경, 오프라인 파티션, 복제 미달 파티션, 브로커 재시작 및 ZooKeeper 세션 만료를 언급하는 로그를 관찰하세요. 단순한 프로세스 사망이 아닌 반복되는 패턴에 대해 경고하세요.
더 큰 업그레이드를 계획하는 팀에게 가장 깔끔한 수정은 ZooKeeper에서 Kafka의 KRaft 모드로 마이그레이션하는 것일 수 있습니다. 그것은 프로젝트이지 인시던트 대응 단계가 아닙니다. 버전 계획, 호환성 확인 및 신중한 마이그레이션 작업이 필요합니다. 그때까지는 ZooKeeper를 프로덕션 인프라로 취급하세요. 작고, Kafka에 가깝고, 일관되게 구성되고, 모니터링되고, 지루하게 유지하세요.
실용적인 런북 패턴 중 하나는 인시던트 중에 작은 매트릭스를 구축하는 것입니다. 한 축에 Kafka 브로커를 배치하고 다른 축에 ZooKeeper 노드를 배치하세요. 각 셀을 nc -vz host 2181의 결과와 가능한 경우 간단한 ZooKeeper 상태 확인으로 채우세요. 이것은 모호한 "Kafka가 ZooKeeper에 연결할 수 없음" 보고서를 눈에 보이는 패턴으로 바꿉니다. 모든 브로커가 zk02에 도달하지 못하면 zk02 또는 해당 네트워크 경로를 조사하세요. broker-4만 모든 ZooKeeper 노드에 도달하지 못하면 해당 브로커의 호스트, 라우트 테이블, DNS 또는 방화벽을 조사하세요.
시간 동기화도 중요할 수 있습니다. ZooKeeper 세션 메커니즘은 모든 작업에 완벽하게 동일한 벽시계를 요구하지 않지만, 심하게 편향된 시계는 로그를 해석하기 어렵게 만들고 주변 자동화, 인증서 및 모니터링을 손상시킬 수 있습니다. Kafka 및 ZooKeeper 노드에서 NTP 또는 chrony를 정상 상태로 유지하세요. 장애 중에 타임스탬프가 일치하지 않으면 사람들은 잘못된 이벤트 순서를 쫓느라 시간을 낭비합니다.
컨테이너화되거나 오케스트레이션된 ZooKeeper 배포에 주의하세요. ZooKeeper는 디스크에 ID와 데이터를 저장합니다. 포드가 이동하여 영구 ID를 잃거나 서비스 검색이 준비되지 않은 노드를 클라이언트에 가리키면 Kafka가 불안정한 연결 동작을 볼 수 있습니다. StatefulSet 스타일 ID, 영구 볼륨, 안정적인 DNS 및 준비 상태 확인이 중요합니다. ZooKeeper 앙상블은 일회용 상태 비저장 웹 포드 세트처럼 동작해서는 안 됩니다.
보안 설정은 또 다른 계층을 추가합니다. SASL, TLS 또는 네트워크 정책이 최근에 도입된 경우 연결 실패가 처음에는 단순한 연결 가능성 문제처럼 보일 수 있습니다. Kafka 로그에 TCP 타임아웃이 아닌 인증 실패, 핸드셰이크 실패 또는 권한 부여 오류가 표시되는지 확인하세요. 브로커가 ZooKeeper에 인증할 수 없기 때문에 포트가 열려 있는 동안에도 세션이 여전히 실패할 수 있습니다.
인시던트 후에는 정확한 증상, 근본 원인 및 수정 사항에 대한 짧은 기록을 보관하세요. ZooKeeper 문제는 원래 복구가 국지적이었기 때문에 종종 반복됩니다: 하나의 방화벽 규칙, 하나의 브로커 재시작, 하나의 타임아웃 증가. 좋은 사후 인시던트 노트는 클러스터에 쿼럼이 있었는지, 어떤 브로커가 세션을 잃었는지, ISR이 축소되었는지, 컨트롤러가 변경되었는지, 그리고 다음에 더 일찍 포착하기 위해 어떤 모니터링이 필요한지 말해야 합니다.
Kubernetes 또는 다른 스케줄러에서 문제를 해결하는 경우 Kafka 및 ZooKeeper 워크로드가 배치된 위치도 확인하세요. 노드 수준의 네트워크 문제, 디스크 문제 또는 CPU 부족 이벤트는 거기에 예약된 포드에만 영향을 미칠 수 있습니다. 포드를 이동하면 문제가 해결된 것처럼 보일 수 있지만 실제 문제는 호스트일 수 있습니다. 애플리케이션이 복구되었다고 선언하기 전에 이벤트와 노드 메트릭을 비교하세요.
백업 및 스냅샷은 주의가 필요합니다. 백업 방법이 이를 위해 설계되지 않은 경우 프로세스가 활성화된 동안 ZooKeeper 데이터 디렉토리를 함부로 스냅샷해서는 안 됩니다. Kafka 메타데이터의 경우 손상되거나 오래된 ZooKeeper 상태는 매우 파괴적일 수 있습니다. ZooKeeper 지원 백업 방식을 따르고 프로덕션에서 복원 절차를 테스트하세요. 아무도 복원하지 않은 백업은 희망적인 파일일 뿐입니다.
가장 좋은 예방 조치는 ZooKeeper를 지루하게 유지하는 것입니다. 가능하면 무거운 Kafka 브로커와 함께 배치하지 마세요. 디스크를 안정적으로 유지하세요. 힙 크기를 보수적이고 모니터링되게 유지하세요. 앙상블 멤버십을 변경할 수 있는 사람을 제한하세요. 제가 본 대부분의 ZooKeeper 인시던트는 이국적인 버그로 인해 발생하지 않았습니다. 모두가 중요하다는 것을 잊은 작은 서비스 주변의 일반적인 인프라 드리프트에서 비롯되었습니다.