Engineering Note

Kafka 브로커 빼기 전에 먼저 Cordon부터

Apache Kafka 4.3에 추가된 cordoned.log.dirs 설정의 의미와 사용법을 정리합니다. 브로커나 log directory를 비우기 전에 새 replica 배치를 먼저 막는 운영 흐름을 다룹니다.

2026년 7월 2일 · Pletor Engineering kafkaoperationsstoragedecommissionmaintenance

Kafka cluster를 운영하다 보면 브로커를 빼야 하는 순간이 옵니다.

클러스터를 줄이거나, 장비를 반납하거나, 특정 브로커의 디스크를 교체해야 할 수 있습니다. JBOD 구성에서는 브로커 전체가 아니라 특정 log directory만 제거해야 하는 경우도 있습니다.

이때 문제는 단순히 “데이터를 옮기면 된다”가 아닙니다.

옮기는 동안 새 replica가 다시 그 디스크나 브로커에 배치될 수 있다.

이런 상황에서는 운영자가 partition reassignment를 준비하고, 진행 상태를 보고, throttle을 조정하고, ISR과 lag을 확인하는 동안 Kafka의 새 partition placement까지 같이 신경 써야 합니다. 브로커나 디스크를 비우고 있는데 새 topic이나 partition이 만들어지면서 다시 해당 위치에 replica가 들어오면 작업은 지저분해집니다.

Apache Kafka 4.3에는 이 문제를 다루기 위한 작은 설정이 추가됐습니다.

cordoned.log.dirs

이 글에서는 Kafka 4.3의 cordoned.log.dirs가 무엇을 해결하는지, 어떻게 쓰는지, 그리고 어떤 한계를 갖는지 정리합니다.

cordon은 drain이 아니다

cordon이라는 표현은 Kubernetes에서 익숙합니다. Node를 cordon하면 새 workload가 그 node에 schedule되지 않습니다. 하지만 이미 떠 있는 workload가 자동으로 사라지는 것은 아닙니다. 그것은 drain의 영역입니다.

Kafka의 cordoned.log.dirs도 비슷하게 이해하면 됩니다.

cordon:
  새 replica가 그 log directory에 배치되지 않도록 막는다.

drain:
  이미 있는 replica를 다른 broker나 log directory로 옮긴다.

중요한 점은 cordoned.log.dirs가 기존 replica를 자동으로 옮겨주지 않는다는 것입니다.

Cordoned log directory는 계속 동작합니다. 이미 그 디렉터리에 있는 replica는 read/write를 계속 처리합니다. 다만 Kafka controller가 새 topic, 새 partition, reassignment 같은 작업에서 replica placement를 계산할 때 해당 log directory를 새 배치 대상으로 보지 않게 됩니다.

즉 이 기능은 장애 복구 자동화라기보다 운영 작업의 첫 단계를 안정화하는 장치입니다.

먼저 새 배치를 막고,
그 다음 기존 replica를 계획적으로 옮긴다.

왜 필요했나

기존 Kafka에서도 partition reassignment로 replica를 옮길 수 있었습니다. 브로커를 추가하거나, 브로커를 줄이거나, log directory 사이에서 replica를 이동하는 일은 이미 가능합니다.

하지만 placement 제어는 충분히 세밀하지 않았습니다.

브로커를 decommission하려고 해당 브로커의 replica를 다른 곳으로 옮기는 중이라고 해보겠습니다. 그 사이에 새 topic이 만들어지거나 partition이 늘어나면, Kafka는 여전히 그 브로커를 새 replica 후보로 볼 수 있습니다. 운영자는 옮기는 작업과 새로 들어오는 작업을 동시에 추적해야 합니다.

JBOD 환경에서는 더 까다롭습니다. 특정 디스크를 제거하려고 replica를 옮기면 그 log directory의 partition 수가 줄어듭니다. Kafka가 새 partition을 배치할 때 “partition 수가 적은 directory”를 고르는 방식과 만나면, 방금 비우고 있던 디스크가 다시 매력적인 후보가 될 수 있습니다.

운영자 입장에서는 원하는 순서가 있습니다.

1. 이 broker나 disk에는 더 이상 새 replica를 넣지 않는다.
2. 기존 replica만 천천히 다른 곳으로 옮긴다.
3. 비워졌는지 확인한다.
4. 디스크나 브로커를 제거한다.

cordoned.log.dirs는 이 중 첫 번째 단계를 Kafka가 이해할 수 있는 설정으로 만든 것입니다.

어떻게 동작하나

cordoned.log.dirs는 broker별 설정입니다.

값은 cordon할 log directory 목록입니다. 이 목록의 값은 해당 broker의 log.dirs 또는 log.dir에 들어 있는 경로여야 합니다.

예를 들어 broker 1이 다음 log directory를 사용한다고 하겠습니다.

log.dirs=/data/dir1,/data/dir2

이 중 /data/dir1만 cordon하려면 broker 1에 다음 dynamic broker config를 설정합니다.

bin/kafka-configs.sh --bootstrap-server localhost:9092 \
  --alter \
  --add-config cordoned.log.dirs=/data/dir1 \
  --entity-type brokers \
  --entity-name 1

여러 디렉터리를 cordon하려면 comma-separated list를 사용합니다.

bin/kafka-configs.sh --bootstrap-server localhost:9092 \
  --alter \
  --add-config cordoned.log.dirs=/data/dir1,/data/dir2 \
  --entity-type brokers \
  --entity-name 1

특별한 값으로 *도 사용할 수 있습니다.

bin/kafka-configs.sh --bootstrap-server localhost:9092 \
  --alter \
  --add-config 'cordoned.log.dirs=*' \
  --entity-type brokers \
  --entity-name 1

*는 해당 broker의 모든 log directory를 cordon합니다. 결과적으로 그 broker는 새 replica placement 대상에서 빠집니다. 그래서 브로커를 scale-in하거나 decommission하려는 경우에는 cordoned.log.dirs=*가 broker cordon처럼 동작합니다.

설정 확인은 일반 broker config describe로 할 수 있습니다.

bin/kafka-configs.sh --bootstrap-server localhost:9092 \
  --describe \
  --entity-type brokers \
  --entity-name 1

Kafka 4.3에서는 log directory describe 응답에도 cordoned 여부가 포함됩니다. 운영 도구나 Admin API를 쓰는 환경이라면 Admin.describeLogDirs()LogDirDescription#isCordoned() 값을 함께 볼 수 있습니다.

디스크를 제거하는 흐름

특정 log directory를 제거하려는 경우 흐름은 다음과 같습니다.

1. 제거할 log directory를 cordon한다.
2. 그 directory에 있는 replica를 다른 log directory나 broker로 이동한다.
3. reassignment가 끝났는지 확인한다.
4. broker를 중지한다.
5. broker 설정의 log.dirs에서 제거할 directory를 뺀다.
6. cordoned.log.dirs 설정을 제거한다.
7. broker를 다시 시작한다.

예를 들어 broker 1의 /data/dir1을 제거하려면 먼저 cordon합니다.

bin/kafka-configs.sh --bootstrap-server localhost:9092 \
  --alter \
  --add-config cordoned.log.dirs=/data/dir1 \
  --entity-type brokers \
  --entity-name 1

그 다음 기존 replica를 다른 곳으로 옮깁니다.

이 단계는 자동이 아닙니다. Kafka operations 문서도 log directory decommission에 대해 partition reassignment tool이 decommission plan을 자동 생성하지 않으므로, 운영자가 이동 계획을 만들어야 한다고 설명합니다.

reassignment가 끝났다면 broker를 중지하고, broker 설정에서 제거할 directory를 뺍니다.

# before
log.dirs=/data/dir1,/data/dir2

# after
log.dirs=/data/dir2

broker가 내려간 상태에서 cordon 설정을 제거해야 한다면 controller bootstrap을 사용합니다.

bin/kafka-configs.sh --bootstrap-controller localhost:9093 \
  --alter \
  --delete-config cordoned.log.dirs \
  --entity-type brokers \
  --entity-name 1

그 후 broker를 다시 시작합니다.

클러스터 scale-in에도 쓸 수 있다

cordoned.log.dirs=*는 Kafka cluster scale-in에도 유용합니다.

브로커 수를 줄이려면 제거할 broker가 더 이상 새 replica placement 대상이 되지 않아야 합니다. 그렇지 않으면 운영자가 broker를 비우는 동안 새 topic 생성, partition 증가, reassignment 같은 작업을 통해 replica가 다시 들어올 수 있습니다.

Scale-in 관점에서 cordoned.log.dirs=*는 다음 의미를 갖습니다.

이 broker는 아직 살아 있고 기존 replica는 처리하지만,
새 replica를 더 배치할 곳은 아니다.

이 상태를 먼저 만들어두면 이후 작업은 훨씬 명확해집니다.

1. 제거 대상 broker를 cordon한다.
2. 기존 replica를 다른 broker로 drain한다.
3. broker가 안전하게 제거 가능한 상태인지 확인한다.

즉 broker를 drain하기 전에 cordon을 설정하면, 비우는 도중 새 replica가 다시 들어오는 상황을 막고 제거 가능한 상태로 만드는 과정을 더 안전하게 진행할 수 있습니다.

브로커를 제거하는 흐름

브로커 전체를 제거할 때는 모든 log directory를 cordon합니다.

bin/kafka-configs.sh --bootstrap-server localhost:9092 \
  --alter \
  --add-config 'cordoned.log.dirs=*' \
  --entity-type brokers \
  --entity-name 1

이 상태에서 broker 1은 새 replica 배치 대상에서 제외됩니다. 기존 replica는 계속 동작하므로, 운영자는 그 replica들을 다른 broker로 reassignment해야 합니다. Cluster scale-in에서는 이 단계가 제거 대상 broker를 안전하게 drain하기 위한 시작점입니다.

흐름은 다음과 같습니다.

1. cordoned.log.dirs=* 로 broker를 cordon한다.
2. broker가 들고 있는 모든 replica를 다른 broker로 옮긴다.
3. reassignment 완료를 확인한다.
4. broker를 중지한다.
5. cluster에서 broker를 unregister한다.

Kafka 4.3 operations 문서는 broker 제거 마지막 단계에서 kafka-cluster.sh unregister를 사용합니다.

bin/kafka-cluster.sh unregister \
  --bootstrap-server localhost:9092 \
  --id 1

여기서도 핵심은 같습니다.

cordon은 새 배치를 막는다.
기존 replica 이동은 여전히 reassignment 작업이다.

새 partition 생성에는 어떤 영향이 있나

cordoned.log.dirs는 새 placement 후보를 줄입니다. 그래서 cluster 용량과 replication factor를 같이 봐야 합니다.

예를 들어 replication factor가 3인 topic을 만들려고 하는데 usable broker가 2개뿐이라면 topic 생성은 실패해야 합니다. KIP-1066은 이런 경우 INVALID_REPLICATION_FACTOR가 반환된다고 설명합니다.

명시적 replica assignment를 줬는데 해당 broker가 uncordoned log directory를 갖고 있지 않다면 INVALID_REPLICA_ASSIGNMENT가 발생할 수 있습니다. Partition reassignment가 cordoned 상태와 충돌하면 INELIGIBLE_REPLICAKAFKA_STORAGE_ERROR 같은 오류를 만날 수 있습니다.

이 오류들은 기능의 버그라기보다 의도된 보호 장치입니다.

운영자가 "여기에는 새 replica를 두지 말라"고 표시했기 때문에
Kafka가 그 위치를 새 배치 대상으로 쓰지 않는 것이다.

따라서 cordon을 사용할 때는 다음을 같이 확인해야 합니다.

  • 현재 replication factor를 만족할 만큼 uncordoned broker가 남아 있는가?
  • 새 topic이나 partition 증가 작업이 예정되어 있는가?
  • reassignment plan이 cordoned broker나 directory를 target으로 잡고 있지 않은가?
  • JBOD 환경에서 log directory 단위 이동과 broker 단위 이동을 구분했는가?

Kafka 4.3.1에서 주의할 edge case

여기서 한 가지 조심해야 할 점이 있습니다.

운영 테스트에서는 cordoned.log.dirs=*가 설정된 broker가 신규 topic 생성이나 자동 replica placement에서는 제외되는 것을 확인할 수 있었습니다. 하지만 Kafka 4.3.1 기준으로, 수동 partition reassignment에서 해당 broker id를 target replica 목록에 명시적으로 포함하면 실제 reassignment가 진행될 수 있는 동작도 관찰됐습니다.

따라서 현재 운영 관점에서는 cordoned.log.dirs=*를 다음처럼 이해하는 편이 안전합니다.

cordoned.log.dirs=*
= 자동 replica placement 제외
≠ 명시적 수동 reassignment 차단

KIP-1066은 AlterPartitionReassignments 요청이 uncordoned log directory가 없는 broker에 replica를 배치하려 하면 INELIGIBLE_REPLICA를 반환한다고 설명합니다. 따라서 이 동작은 문서상 기대와 다르게 보이는 edge case입니다. 다만 실제 운영에서는 사용 중인 Kafka 버전의 동작이 우선입니다.

Broker drain이나 decommission 자동화를 만들 때는 reassignment 실행 전에 target replica 목록에 cordoned broker가 포함되어 있는지 별도로 검증해야 합니다.

CORDONED_BROKERS="6 7"

for broker in $CORDONED_BROKERS; do
  if jq -e --argjson b "$broker" \
    '.partitions[] | select(.replicas[] == $b)' \
    reassignment.json >/dev/null; then
    echo "ERROR: reassignment plan contains cordoned broker $broker"
    exit 1
  fi
done

정리하면, cordon은 Kafka가 자동으로 새 replica를 배치하지 않도록 하는 운영 guardrail입니다. 관리자가 명시한 reassignment plan까지 검증하는 최종 안전장치로 신뢰해서는 안 됩니다.

모니터링할 수 있나

KIP-1066은 cordoned log directory를 보기 위한 metric도 추가합니다.

kafka.log:type=LogManager,name=LogDirectoryCordoned,logDirectory="<PATH>"
kafka.log:type=LogManager,name=CordonedLogDirectoryCount

첫 번째 metric은 특정 log directory가 cordoned 상태인지 0 또는 1로 보여줍니다. 두 번째 metric은 broker 안에서 cordoned 상태인 log directory 개수를 보여줍니다.

운영 대시보드에는 다음 정도를 같이 두는 것이 좋습니다.

  • broker별 cordoned log directory count
  • offline log directory count
  • disk usage
  • partition reassignment 진행 상태
  • under replicated partition
  • ISR shrink/expand
  • produce/fetch latency

cordoned.log.dirs는 유지보수 작업의 의도를 Kafka에 전달하는 신호입니다. 따라서 이 신호가 실제 replica 이동, ISR 안정성, 디스크 사용량 변화와 함께 보일 때 운영 판단이 쉬워집니다.

Kubernetes cordon/drain과 비슷하지만 같지는 않다

이 기능은 Kubernetes의 cordon과 닮았습니다. 그래서 직관적으로 이해하기 좋습니다.

하지만 완전히 같지는 않습니다.

Kubernetes cordon:
  새 Pod scheduling을 막는다.

Kafka cordoned.log.dirs:
  새 partition replica placement를 막는다.

그리고 둘 다 “이미 있는 것”을 자동으로 옮기지는 않습니다.

Kafka에서는 drain에 해당하는 작업이 partition reassignment입니다. 아직 Kafka는 broker나 log directory decommission plan을 자동으로 만들어주지 않습니다. 운영자는 어떤 replica를 어디로 옮길지 계획해야 합니다.

그래도 cordoned.log.dirs가 의미 있는 이유는 분명합니다. 적어도 비우고 있는 곳에 새 replica가 다시 들어오는 상황을 막을 수 있기 때문입니다.

운영 작업에서 이 차이는 큽니다.

이전:
  비우면서 동시에 새 배치까지 감시해야 했다.

이후:
  먼저 cordon해서 새 배치를 막고,
  기존 replica 이동에 집중할 수 있다.

이런 운영 흐름은 Konduo의 Kafka broker drain 기능과도 연결됩니다. Konduo는 Kafka broker 상태와 metric evidence를 확인하면서 drain 작업을 진행할 수 있도록 설계되어 있고, 대상 cluster가 cordoned.log.dirs를 지원하며 조건이 맞는다면 broker drain 전에 가능한 먼저 cordon을 적용할 수 있습니다. Cordon이 drain을 대체하는 것은 아니지만, 새 replica placement를 먼저 막고 기존 replica 이동을 진행하는 순서를 운영 흐름 안에 포함할 수 있다는 점이 중요합니다.

정리

Kafka 4.3의 cordoned.log.dirs는 화려한 기능은 아닙니다. 하지만 실제 운영에서는 꽤 중요한 기능입니다.

브로커를 scale-in하거나 decommission할 때, 또는 디스크를 교체하거나 JBOD log directory를 제거할 때 가장 먼저 필요한 것은 “여기에 더 이상 새 replica를 넣지 말라”는 의도를 cluster에 전달하는 것입니다.

cordoned.log.dirs는 그 의도를 Kafka의 placement logic이 이해할 수 있는 형태로 만듭니다.

다만 오해하면 안 됩니다.

  • 기존 replica는 계속 동작합니다.
  • 기존 replica가 자동으로 이동하지는 않습니다.
  • reassignment plan은 여전히 운영자가 준비해야 합니다.
  • Kafka 4.3.1에서는 수동 reassignment plan이 cordoned broker를 포함하지 않는지 별도로 검증해야 합니다.
  • usable broker/log directory가 부족하면 topic 생성이나 reassignment가 실패할 수 있습니다.

그래서 이 기능은 “자동 decommission”이 아니라 “안전한 decommission을 시작하기 위한 cordon”입니다.

운영 절차로 정리하면 간단합니다.

cordon first.
then reassign.
then remove.

Kafka 브로커 제거와 디스크 교체 작업은 이제 이 순서로 시작하는 편이 더 안전합니다.

함께 읽기 좋은 글

이 글의 관점과 이어지는 주제를 더 보려면 아래 글도 함께 읽어볼 수 있습니다.

References