Part 3: Kafka 운영
지원 버전: Strimzi 0.45+, Kafka 3.9
마지막 업데이트: 2026년 7월 9일
Strimzi Operator로 Kafka 클러스터를 배포한 이후에는 스토리지 용량 계획, 브로커 스케일링, 파티션 재배치, 무중단 업그레이드 같은 운영 작업이 이어집니다. 이 문서는 EKS 위에서 Strimzi가 관리하는 Kafka 클러스터를 실제로 운영할 때 마주치는 핵심 작업을 다룹니다.
스토리지 설계
EBS 볼륨 타입 선택: gp3 vs io2
Kafka 브로커의 로그 세그먼트는 순차 쓰기/읽기가 대부분이지만, 컨슈머 랙(consumer lag)이 커지면 오래된 세그먼트에 대한 랜덤 읽기가 발생할 수 있습니다. EBS 볼륨 타입은 이 특성에 맞춰 선택합니다.
| 항목 | gp3 | io2 |
|---|---|---|
| 과금 방식 | 용량 기준, IOPS/처리량은 별도 프로비저닝 | IOPS 기준 (더 높은 단가) |
| 처리량 | 기본 125MB/s, 최대 1,000MB/s로 개별 조정 가능 | 볼륨 크기와 IOPS에 연동 |
| 최대 IOPS | 16,000 | 256,000 |
| 적합한 워크로드 | 대부분의 Kafka 워크로드 — 처리량이 병목인 경우 | 컨슈머 랙 급증, 다수의 소규모 랜덤 I/O가 빈번한 지연시간 민감 워크로드 |
| 내구성(연간 오류율) | 99.8~99.9% | 99.999% |
일반적인 이벤트 스트리밍 워크로드는 gp3로 시작해 처리량(throughput)과 IOPS를 필요에 맞게 개별 프로비저닝하는 것이 비용 효율적입니다. 다수의 컨슈머 그룹이 서로 다른 오프셋에서 동시에 읽어 랜덤 I/O 비중이 높거나, p99 지연시간 SLA가 엄격한 경우에만 io2로 전환을 고려합니다.
JBOD를 이용한 멀티 볼륨 스토리지
Strimzi는 브로커당 여러 개의 독립된 볼륨을 사용하는 JBOD(Just a Bunch Of Disks) 구성을 지원합니다. 하나의 대형 볼륨 대신 여러 개의 볼륨으로 나누면 볼륨 단위로 처리량을 병렬화할 수 있고, 볼륨을 개별적으로 추가/교체할 수 있습니다.
apiVersion: kafka.strimzi.io/v1beta2
kind: KafkaNodePool
metadata:
name: broker
labels:
strimzi.io/cluster: my-cluster
spec:
replicas: 3
roles:
- broker
storage:
type: jbod
volumes:
- id: 0
type: persistent-claim
size: 500Gi
class: gp3-encrypted
deleteClaim: false
- id: 1
type: persistent-claim
size: 500Gi
class: gp3-encrypted
deleteClaim: false
resources:
requests:
memory: 8Gi
cpu: "2"
limits:
memory: 8Gi
cpu: "4"각 volumes 항목의 id는 브로커 내에서 로그 디렉터리를 구분하는 데 사용되며, 파티션은 라운드 로빈 방식으로 볼륨에 분산 배치됩니다. deleteClaim: false는 브로커 스케일 다운이나 재생성 시 PVC가 삭제되지 않도록 보호합니다.
참고: Strimzi를 사용하면 브로커 파드가 시작될 때
kafka-storage.sh format을 Operator가 자동으로 처리하므로, 볼륨 포맷을 위해 이 스크립트를 직접 실행할 필요가 없습니다.
스토리지 사이징 가이드
디스크 용량은 다음 공식을 기준으로 산정합니다.
필요 디스크 용량 = 보존 기간 × 피크 처리량(초당 바이트) × 복제 팩터 × (1 + 헤드룸 비율)예를 들어 피크 처리량 50MB/s, 보존 기간 7일(604,800초), 복제 팩터 3, 헤드룸 30%를 가정하면:
50MB/s × 604,800s × 3 × 1.3 ≈ 118TB (클러스터 전체)브로커 3대로 나누면 브로커당 약 39TB가 필요합니다. 헤드룸을 반드시 확보해야 하는 이유는, Kafka 브로커는 디스크 사용률이 임계치(기본적으로 로그 클리너와 세그먼트 롤링 동작에 영향)를 넘으면 성능이 급격히 저하되고, log.retention.bytes/log.retention.hours 기준 삭제가 지연되면 디스크가 가득 차 브로커가 오프라인 상태가 될 수 있기 때문입니다. 최소 20~30%의 여유 공간을 항상 유지하는 것을 권장합니다.
브로커/컨트롤러 스케일링
브로커 스케일 아웃
KafkaNodePool의 replicas 값을 늘리면 Strimzi가 새 브로커 파드를 생성하고 클러스터에 자동으로 합류시킵니다.
kubectl patch kafkanodepool broker -n kafka --type=merge \
-p '{"spec":{"replicas":6}}'
# 새 브로커가 클러스터에 합류했는지 확인
kubectl get pods -n kafka -l strimzi.io/pool-name=broker새로 추가된 브로커는 기존 파티션의 리더/팔로워로 자동 선출되지 않습니다. 기존 토픽의 파티션을 새 브로커로 재분배하려면 별도의 파티션 재배치 작업이 필요합니다.
파티션 재배치 (kafka-reassign-partitions.sh)
# 1) 재배치 대상 토픽을 정의하는 JSON을 브로커 파드 내부에 작성
kubectl exec -it my-cluster-broker-0 -n kafka -- bash -c 'cat <<EOF > /tmp/topics-to-move.json
{
"topics": [{"topic": "orders"}, {"topic": "payments"}],
"version": 1
}
EOF'
# 2) 브로커 ID 목록을 지정해 재배치 계획을 생성하고, 파드 내부 파일로 저장
kubectl exec -it my-cluster-broker-0 -n kafka -- bash -c '
bin/kafka-reassign-partitions.sh \
--bootstrap-server localhost:9092 \
--topics-to-move-json-file /tmp/topics-to-move.json \
--broker-list "0,1,2,3,4,5" \
--generate > /tmp/generate-output.txt
# --generate 출력에는 현재 배치(Current)와 제안 배치(Proposed) JSON이 함께 들어있으므로,
# "Proposed partition reassignment configuration" 아래의 JSON만 추출해 저장
awk "/^Proposed partition reassignment configuration/{flag=1; next} flag" /tmp/generate-output.txt > /tmp/reassignment.json
'
# 3) 생성된 계획(reassignment.json)을 실제로 적용
kubectl exec -it my-cluster-broker-0 -n kafka -- \
bin/kafka-reassign-partitions.sh \
--bootstrap-server localhost:9092 \
--reassignment-json-file /tmp/reassignment.json \
--execute
# 4) 진행 상태 확인
kubectl exec -it my-cluster-broker-0 -n kafka -- \
bin/kafka-reassign-partitions.sh \
--bootstrap-server localhost:9092 \
--reassignment-json-file /tmp/reassignment.json \
--verify브로커 스케일 다운은 왜 위험한가
Strimzi는 브로커를 스케일 다운할 때 파티션을 자동으로 드레인(drain)하지 않습니다. KafkaNodePool의 replicas를 줄이기 전에, 제거할 브로커에 할당된 모든 파티션(리더 및 팔로워 레플리카 포함)을 남아 있는 브로커로 먼저 재배치해야 합니다. 이 순서를 지키지 않으면 해당 브로커에만 존재하던 레플리카가 사라져 언더 리플리케이트(under-replicated) 파티션이 발생하거나, 최악의 경우 데이터 손실로 이어질 수 있습니다.
안전한 스케일 다운 절차는 다음과 같습니다.
kafka-reassign-partitions.sh --generate로 제거 대상 브로커를 제외한 브로커 목록만으로 재배치 계획 생성--execute로 재배치 적용 후--verify로 완료 확인 (언더 리플리케이트 파티션이 0인지 확인)- 재배치가 완전히 끝난 뒤에만
KafkaNodePool.spec.replicas를 줄여 브로커 파드 제거
Cruise Control을 이용한 자동 리밸런싱
Cruise Control은 브로커 간 디스크 사용량, CPU, 네트워크 처리량 등의 부하 지표를 지속적으로 수집하고, 이를 기반으로 파티션 재배치 계획을 자동으로 생성/실행하는 컴포넌트입니다. 브로커를 추가하거나 제거할 때마다 kafka-reassign-partitions.sh를 수동으로 실행하는 대신, 목표(goal) 기반으로 리밸런싱을 위임할 수 있습니다.
Cruise Control 활성화
apiVersion: kafka.strimzi.io/v1beta2
kind: Kafka
metadata:
name: my-cluster
spec:
kafka:
version: 3.9.0
# ... 기존 kafka 설정 ...
cruiseControl:
config:
# 목표: 디스크/CPU/네트워크 사용량을 브로커 간 균등하게 유지
goals: >-
com.linkedin.kafka.cruisecontrol.analyzer.goals.RackAwareGoal,
com.linkedin.kafka.cruisecontrol.analyzer.goals.DiskCapacityGoal,
com.linkedin.kafka.cruisecontrol.analyzer.goals.CpuCapacityGoal,
com.linkedin.kafka.cruisecontrol.analyzer.goals.NetworkInboundCapacityGoal,
com.linkedin.kafka.cruisecontrol.analyzer.goals.NetworkOutboundCapacityGoalKafkaRebalance 리소스로 리밸런싱 트리거
apiVersion: kafka.strimzi.io/v1beta2
kind: KafkaRebalance
metadata:
name: my-rebalance
namespace: kafka
labels:
strimzi.io/cluster: my-cluster
spec:
mode: full# 리밸런싱 계획 생성 (아직 실행되지 않음, PendingProposal → ProposalReady)
kubectl get kafkarebalance my-rebalance -n kafka -o yaml
# 생성된 계획을 승인해 실제 리밸런싱 실행
kubectl annotate kafkarebalance my-rebalance -n kafka \
strimzi.io/rebalance=approve
# 진행 상태 확인
kubectl get kafkarebalance my-rebalance -n kafka -w리밸런싱 모드
| 모드 | 용도 |
|---|---|
full (기본값) | 클러스터의 모든 브로커를 대상으로 설정된 목표에 따라 전체 리밸런싱 계획 생성 |
add-brokers | 새로 추가된 브로커로만 파티션을 이동시켜 신규 브로커의 부하를 채우는 데 특화 (전체 재배치보다 빠르고 영향 범위가 작음) |
remove-brokers | 제거할 브로커의 파티션만 나머지 브로커로 옮기는 데 특화 — 스케일 다운 전 안전한 드레인 절차로 활용 |
브로커를 추가/제거하는 스케일링 작업 직후에는 add-brokers 또는 remove-brokers 모드로 범위를 좁혀 리밸런싱하면, 관련 없는 파티션까지 불필요하게 이동시키는 full 모드보다 네트워크 부하와 소요 시간을 줄일 수 있습니다.
롤링 업그레이드
CR 스펙 변경에 따른 자동 롤링 재시작
Kafka 또는 KafkaNodePool CR의 스펙(리소스 요청/제한, 설정 값, 볼륨 등)을 변경하면 Strimzi Operator는 이를 감지해 브로커 파드를 한 번에 하나씩 재시작합니다. 이 과정에서 Operator는 각 파티션의 min.insync.replicas를 만족하는 범위 내에서만 재시작을 진행하도록 조율하여, 재시작으로 인해 특정 파티션의 가용 레플리카 수가 요구치 밑으로 떨어지지 않도록 보장합니다.
Kafka 버전 업그레이드 — 2단계 절차
KRaft 모드에서는 ZooKeeper 시절의 inter.broker.protocol.version/log.message.format.version 대신, Kafka CR의 spec.kafka.version(소프트웨어 버전)과 spec.kafka.metadataVersion(KRaft 메타데이터 로그 포맷 버전)을 동시에 올리지 않고 두 단계로 나누어 진행해야 합니다. metadataVersion은 컨트롤러 쿼럼이 메타데이터를 기록하는 포맷을 결정하므로, 신버전과 구버전 브로커/컨트롤러가 혼재하는 롤링 업그레이드 도중에는 이전 포맷을 유지해야 합니다.
1단계 — 소프트웨어 버전만 업그레이드
apiVersion: kafka.strimzi.io/v1beta2
kind: Kafka
metadata:
name: my-cluster
spec:
kafka:
version: 3.9.0
# metadataVersion은 이전 버전으로 유지
metadataVersion: 3.8-IV0이 상태로 적용하면 Strimzi가 브로커/컨트롤러 소프트웨어를 롤링 방식으로 3.9.0으로 교체하지만, 메타데이터 포맷은 여전히 3.8-IV0 방식을 사용합니다. 이렇게 하면 업그레이드 도중 신버전과 구버전 노드가 혼재하는 시점에도 동일한 메타데이터 포맷으로 컨트롤러 쿼럼이 정상 동작합니다.
2단계 — 모든 노드 교체 확인 후 metadataVersion 상향
version: 3.9.0
metadataVersion: 3.9-IV0모든 브로커/컨트롤러가 3.9.0으로 정상 교체된 것을 확인한 뒤에만 metadataVersion을 올립니다. 이 변경 역시 새 메타데이터 포맷을 반영하기 위한 조정을 한 번 더 유발합니다. 순서를 바꿔 소프트웨어 버전과 metadataVersion을 동시에 올리면, 아직 이전 버전으로 실행 중인 노드가 새 메타데이터 포맷을 이해하지 못해 컨트롤러 쿼럼 통신 오류가 발생할 수 있습니다.
Strimzi Operator 버전 업그레이드
Kafka 버전을 올리기 전에 Strimzi Operator 자체를 먼저 최신 버전으로 업그레이드해야 합니다. 각 Strimzi 버전은 지원하는 Kafka 버전 범위가 명시되어 있으며, Operator가 인식하지 못하는 Kafka 버전으로 CR을 변경하면 검증에 실패합니다. 일반적인 순서는 Operator 업그레이드 → Operator가 재조정(reconcile)을 완료할 시간 확보 → Kafka 소프트웨어 버전 업그레이드(1단계) → metadataVersion 업그레이드(2단계)입니다.
장애 대응 기초
PodDisruptionBudget과 브로커 파드 축출
Strimzi는 KafkaNodePool마다 PodDisruptionBudget(PDB)을 자동으로 생성합니다. 기본값은 한 번에 하나의 브로커 파드만 자발적 축출(voluntary eviction) — 노드 드레인, 클러스터 오토스케일러의 노드 교체 등 — 을 허용하도록 설정되어, 여러 브로커가 동시에 다운되어 쿼럼/가용성이 깨지는 상황을 방지합니다.
kubectl get pdb -n kafka -l strimzi.io/cluster=my-clusteracks=all 프로듀서와 롤링 재시작
프로듀서가 acks=all로 설정된 경우, 브로커 롤링 재시작 중에도 데이터 손실 없이 안전하게 동작합니다. 재시작 대상 브로커가 특정 파티션의 리더였다면, 재시작 직전에 컨트롤러가 다른 인-싱크 레플리카(ISR) 중 하나를 새 리더로 선출합니다. 프로듀서는 리더 변경을 감지하면 메타데이터를 갱신하고 새 리더로 요청을 재전송하며, 이 과정에서 일시적인 지연은 발생할 수 있지만 min.insync.replicas를 만족하는 한 커밋된 데이터는 손실되지 않습니다. 반대로 acks=1 이하로 설정된 프로듀서는 재시작 시점에 아직 팔로워로 복제되지 않은 메시지가 유실될 위험이 있습니다.
컨슈머 관점에서는 리밸런스(consumer group rebalance)가 발생해 일시적으로 처리량이 감소할 수 있지만, 컨슈머 오프셋 커밋이 정상적으로 이루어졌다면 재시작 이후 중단 지점부터 처리를 이어갈 수 있습니다.
퀴즈
이 장에서 배운 내용을 테스트하려면 주제 퀴즈를 풀어보세요.
다음 문서: Part 4 — 스키마 레지스트리(Schema Registry)를 이용한 메시지 스키마 관리와 호환성 전략을 다룹니다.