Apache Kafka Connect 실전 정리 시리즈 마지막 5편. 프로덕션 배포(Docker·k8s·Worker 수 산정), 다중 Worker Rolling restart, JMX·Prometheus 모니터링, Dead Letter Queue 에러 처리, tasks.max·batch.size 성능 튜닝, SSL·SASL·ACL 보안까지 본격 운영 단계 비유로 친절하게 정리하며 시리즈 마무리.
여기까지 와줘서 고마워요. 이 글은 Apache Kafka Connect 실전 정리 시리즈의 마지막 5편이자 완결편입니다. 1편에서 표준 케이블 비유로 큰 그림을 잡고, 2편에서 Source·Sink 커넥터를 골라 꽂아 봤고, 3편에서 Distributed Mode와 REST API로 사무소를 차렸으며, 4편에서 SMT와 커스텀 커넥터로 데이터를 가공하는 손맛까지 봤어요. 이제 마지막 단원에서 우리가 다룰 주제는 Kafka Connect 운영 — "테스트 사무소"를 진짜 본격적인 프로덕션 사무소로 옮기는 작업입니다.
이번 편은 프로덕션 배포(Docker·k8s·Worker 수 산정), 다중 Worker Rolling restart, JMX·Prometheus 모니터링, Dead Letter Queue 에러 처리, tasks.max·batch.size·offset.flush.interval.ms 같은 성능 튜닝, SSL·SASL·ACL 보안, CI/CD 흐름 — 이 큰 묶음 일곱 개를 한 자리에서 정리합니다. 양은 많지만 비유는 그대로 유지해요. 본격 운영 단계로 옮기는 사무소 이사 작업이라고 잡으시면 됩니다. 공식 문서가 곁에 있으면 좋아요 — Kafka Connect 공식 문서를 두고 보면 더 든든합니다.
왜 운영 단원이 처음엔 어렵게 느껴질까요
이유는 세 가지예요.
첫째, 다뤄야 할 부품이 갑자기 늘어납니다. 1~4편에서는 케이블 부품과 일꾼만 신경 쓰면 됐는데, 운영 단원에 오면 카메라(JMX), 온도계(Prometheus), 고장난 자료 박스(DLQ), 자물쇠(SSL·SASL·ACL), 배송 트럭(CI/CD)까지 모두 챙겨야 하죠. 부품 수가 두세 배로 늘어나니 막막해 보입니다.
둘째, Kafka Connect 운영은 여러 도메인 지식의 교집합이에요. 분산 시스템 운영, JVM 튜닝, 보안, 컨테이너 오케스트레이션, 모니터링 — 이게 다 한 번에 들어와요. 하나하나 따로 보면 익숙하지만 묶이면 부담스럽죠.
셋째, "개발 환경에서 잘 돌던 게 프로덕션에서 갑자기 안 돈다"는 함정이 가장 무섭습니다. 1대 사무소(Standalone)에서는 보이지 않던 문제가 100배 트래픽에서 한꺼번에 터지거든요.
해결법은 한 가지예요. 운영을 "사무소를 본격 운영 단계로 옮기는 이사"로 잡고, 1~4편에서 쌓아 둔 비유 위에 부품을 하나씩 얹어 가는 거예요. 카메라·온도계·박스·자물쇠·트럭 — 각 도구의 자리를 비유로 박아 두면 운영 단원도 깔끔하게 정리됩니다. 이번 글은 그 흐름을 따라 갑니다.
개발 환경과 프로덕션 환경 — 무엇이 달라야 하나
가장 먼저 짚고 갈 게 개발 환경과 프로덕션 환경의 차이입니다. Kafka Connect 운영의 출발선이에요.
| 항목 | 개발 환경 | 프로덕션 환경 |
|---|---|---|
| 실행 방식 | Docker Compose (Landoop 같은 올인원) | 실제 Kafka 클러스터 + 별도 Connect 클러스터 |
| Worker 수 | 1개 | 최소 3개 |
| 복제 인수 | 1 | 3 이상 |
| 오프셋 저장 | 로컬 파일 (Standalone) | 카프카 내부 토픽 |
| 모니터링 | UI 한 장으로 끝 | JMX·Prometheus·Grafana 별도 구성 |
| 보안 | 보통 무방어 | SSL·SASL·ACL 필수 |
| 설정 관리 | 손으로 편집 | Git 저장소·CI/CD |
비유로 풀면 — 개발 환경은 테스트 사무소(원룸) 이고, 프로덕션 환경은 본사 빌딩이에요. 원룸에서는 일꾼 한 명, 케이블 한 개, 자물쇠 없이 그냥 일해도 별 문제가 없어요. 본사 빌딩에 오면 일꾼이 여럿(다중 Worker), 케이블 백업도 여럿(replication.factor=3), 출입증·자물쇠도 갖춰야 합니다(SSL·SASL·ACL). Kafka Connect 운영의 큰 그림이 이거예요.
여기서 시험 함정이 하나 있어요. "개발 환경은 Docker Compose로 빠르게 띄우고, 프로덕션도 그냥 Worker 수만 늘리면 되는 거 아닌가?"라는 단순화는 위험합니다. 프로덕션은 실행 방식·내부 토픽·모니터링·보안·배포 자동화까지 모두 다른 차원이에요. 처음부터 분리해서 설계해야 합니다.
프로덕션 배포 — 사무소를 본격 운영 단계로 옮기기
이제 본격 이사 작업입니다. Kafka Connect 운영의 첫 단계 — 사무소를 본격 운영 단계로 옮기기로 풀어 갈게요.
0단계: Java 환경 확인
java -version
# java version "1.8.0_xxx"
Connect Worker는 JVM 위에서 도는 일꾼이에요. Java가 깔려 있어야 합니다.
1단계: Apache Kafka 다운로드
wget https://archive.apache.org/dist/kafka/2.6.0/kafka_2.12-2.6.0.tgz
tar -xzf kafka_2.12-2.6.0.tgz
cd kafka_2.12-2.6.0
ls
# bin/ config/ libs/ logs/ site-docs/
개발 환경의 Landoop 올인원 박스에서 벗어나, Apache Kafka 공식 배포본을 직접 다운로드합니다. 본사로 이사 가면서 가구를 직접 들이는 거예요.
2단계: connect-distributed.properties 작성
# 핵심 설정
bootstrap.servers=kafka-broker1:9092,kafka-broker2:9092,kafka-broker3:9092
group.id=connect-cluster-production
# 컨버터
key.converter=org.apache.kafka.connect.json.JsonConverter
key.converter.schemas.enable=true
value.converter=org.apache.kafka.connect.json.JsonConverter
value.converter.schemas.enable=true
# 내부 카프카 토픽 (프로덕션: replication.factor=3)
offset.storage.topic=connect-offsets
offset.storage.replication.factor=3
offset.storage.partitions=25
config.storage.topic=connect-configs
config.storage.replication.factor=3
# config.storage.partitions는 항상 1 (변경 불가)
status.storage.topic=connect-status
status.storage.replication.factor=3
status.storage.partitions=5
# REST API
rest.port=8083
rest.host.name=0.0.0.0
# 오프셋 플러시
offset.flush.interval.ms=60000
# 커넥터 플러그인 경로
plugin.path=/opt/kafka/connectors,/usr/share/java
3편에서 다룬 Distributed Mode 설정이 그대로 이어집니다. 다만 replication.factor가 3이 되고, bootstrap.servers는 여러 브로커를 가리키며, plugin.path가 명확하게 분리돼요. 본사답게 모든 백업이 두꺼워지는 셈이죠.
3단계: 커넥터 JAR 추가
mkdir /path/to/kafka/connectors
cp debezium-connector-postgres/*.jar /path/to/kafka/connectors/
plugin.path에 등록한 디렉터리에 JAR 파일을 갖다 둡니다. 케이블 부품(Connector)을 본사 창고에 정리해 두는 단계예요.
4단계: Connect 클러스터 시작
bin/connect-distributed.sh config/connect-distributed.properties &
# 시작 확인
curl http://localhost:8083/
# {"version":"2.6.0","commit":"...", "kafka_cluster_id":"..."}
curl http://localhost:8083/connectors
# []
여기서 시험 함정이 하나 있어요. 처음 시작했을 때 curl /connectors가 빈 배열을 돌려준다고 해서 "뭔가 잘못됐나?" 싶을 수 있는데 — 신생 클러스터는 당연히 비어 있어요. 커넥터는 별도로 POST로 등록해야 생성됩니다(3편 참고).
Docker·k8s 배포는 어떻게 다른가
본사 이사 방식은 크게 세 갈래예요.
- 베어메탈/VM — Apache Kafka 배포본 풀어서 systemd 서비스로 등록. 가장 직관적이지만 자동화는 직접 짜야 함.
- Docker —
confluentinc/cp-kafka-connect같은 공식 이미지를 환경 변수로 설정. 컨테이너 오케스트레이터 없이도 빠르게 띄울 수 있음. - Kubernetes — Strimzi·Confluent for Kubernetes 같은 오퍼레이터로
KafkaConnect리소스를 선언. 롤링 업데이트·헬스체크·자동 스케일링이 기본 제공되는 게 가장 큰 장점.
여기서 시험 함정이 하나 있어요. "Docker로 띄우면 Kubernetes 안 써도 되지 않나?"라는 단순화는 큰 회사에선 안 통합니다. Docker만으로는 Worker 자동 복구·스케일링·롤링 업데이트가 안 돼요. Kubernetes 위에 오퍼레이터를 얹어야 비로소 본격 운영이 됩니다.
> 한 줄 정리 — 프로덕션 배포 = "사무소를 본격 운영 단계로 옮기기" / Apache Kafka 직접 다운로드 → connect-distributed.properties → 커넥터 JAR → connect-distributed.sh → (가능하면 k8s + 오퍼레이터).
Worker 수는 어떻게 산정하나
본사 빌딩에 일꾼을 몇 명 둘지가 다음 질문이에요. Kafka Connect 운영에서 가장 자주 묻는 질문 중 하나입니다.
클러스터 규모별 권장 Worker 수:
소규모 (< 10개 커넥터): 3 Workers
중규모 (10~50개 커넥터): 5~10 Workers
대규모 (50+ 커넥터): 10+ Workers
원칙:
- 가용성 위해: 최소 3개 Worker (1개 장애에도 운영 가능)
- 부하 분산: Task 수 >= Worker 수 권장
- 확장: Task를 추가하면 기존 Worker들이 자동으로 분담
비유로 풀면 — 본사 빌딩 한 층에 일꾼 한 명으로는 한 명만 결근해도 전체가 멈춰요. 그래서 최소 세 명부터 시작합니다. 일이 늘어 케이블 줄(Task)이 많아지면 일꾼도 같이 늘리면 돼요. 핵심 원칙은 Task 수 >= Worker 수입니다. 이 원칙을 어기면 일꾼은 많은데 일거리가 부족해 노는 일꾼이 생기죠.
여기서 시험 함정이 하나 있어요. "트래픽이 적으니까 Worker 1대로도 충분하다"라는 판단은 위험합니다. Worker 1대는 본질적으로 Standalone과 다를 게 없어요. 그 1대가 죽으면 모든 게 멈추니까요. 가용성이 목적이라면 무조건 3대 이상입니다. Kafka Connect 운영의 첫 번째 룰이에요.
다중 Worker 추가하기
# 첫 번째 Worker용 설정 파일 복사
cp config/connect-distributed.properties \
config/connect-distributed-worker2.properties
# 두 번째 Worker 설정 파일 편집 — REST 포트만 변경
# rest.port=8083 → rest.port=8084
# 두 번째 Worker 시작
bin/connect-distributed.sh \
config/connect-distributed-worker2.properties &
같은 클러스터에 합류하려면 다음 다섯 가지가 동일해야 해요.
bootstrap.serversgroup.idoffset.storage.topicconfig.storage.topicstatus.storage.topicplugin.path
다른 건 REST 포트뿐이에요. 이게 같은 사무소(group.id)에 출근하는 일꾼들의 사번 차이라고 보면 됩니다.
# 첫 번째 Worker
curl http://localhost:8083/ | jq .
# 두 번째 Worker
curl http://localhost:8084/ | jq .
# 두 응답이 같은 kafka_cluster_id를 가지면 동일 클러스터에 속함
{
"version": "2.6.0",
"kafka_cluster_id": "abc123-same-for-both"
}
kafka_cluster_id가 같으면 같은 사무소 식구예요. 같이 일을 분담합니다.
Rolling restart — 일꾼들을 한 명씩 교대로 쉬게 하기
운영 단원의 꽃이 Rolling restart예요. Kafka Connect 운영에서 가장 자주 쓰는 무중단 배포 기법입니다.
비유로 풀면 — 사무소 일꾼들이 작업복을 새로 맞춰 입어야 한다고 칩시다. 다섯 명이 다 같이 동시에 옷을 갈아입으면 일이 멈추죠. 한 명씩 교대로 쉬게 해서 그 사이 다른 네 명이 일을 맡아 주는 방식 — 이게 Rolling restart예요. 서비스 중단 없이 Worker 코드·설정·JVM 버전을 바꿀 수 있습니다.
Rolling restart 절차:
[기존: Worker1, Worker2, Worker3 정상 가동]
1. Worker1 중지 → 자동 재균형으로 Task가 Worker2·3로 이동
2. Worker1 새 버전으로 시작 → 자동 재균형으로 Task 다시 분배
3. Worker2 중지 → 자동 재균형
4. Worker2 새 버전으로 시작
5. Worker3 중지 → 자동 재균형
6. Worker3 새 버전으로 시작
[완료: 전체 클러스터 새 버전, 서비스 중단 없음]
핵심은 1편에서 본 자동 재균형(Rebalancing) 이에요. Worker가 사라지면 그 일꾼이 들고 있던 Task가 다른 일꾼에게 자동으로 넘어가요. 이 메커니즘 덕분에 한 명씩 빼도 일이 멈추지 않습니다.
여기서 시험 함정이 하나 있어요. "Rolling restart를 빠르게 진행하려고 두 명을 동시에 쉬게 하면 안 되나?"라는 단축 시도는 위험합니다. Worker 3대 중 동시에 2대를 빼면 남은 1대에 Task가 쏠려 과부하가 와요. 반드시 한 명씩, 안정 확인 후 다음 사람이 정석입니다. 운영팀에서 자주 부르는 룰로 "한 번에 한 명만 쉰다"가 있어요.
Worker 장애 시나리오
자동 재균형의 위력을 한 번 더 봅시다.
초기 상태 (Worker 4개, 커넥터 3개):
Worker1: Connector1-Task1, Connector2-Task1
Worker2: Connector1-Task2, Connector2-Task2
Worker3: Connector1-Task3, Connector3-Task1
Worker4: Connector3-Task2, Connector3-Task3, Connector3-Task4
↑ Worker4 장애 발생
자동 재균형 후:
Worker1: Connector1-Task1, Connector2-Task1, Connector3-Task2
Worker2: Connector1-Task2, Connector2-Task2, Connector3-Task3
Worker3: Connector1-Task3, Connector3-Task1, Connector3-Task4
Worker4의 Task 세 개가 1·2·3에 자동으로 한 개씩 분배됐어요. 개발자가 손댈 게 없는 무중단 운영, 이게 Distributed Mode의 자랑입니다.
> 한 줄 정리 — Rolling restart = "일꾼들을 한 명씩 교대로 쉬게 하기 (서비스 안 끊고)" / 자동 재균형이 백업 일꾼 역할.
모니터링 — 사무소 곳곳에 단 카메라·온도계
본사 빌딩에 일꾼들이 자리 잡았으면 그다음은 모니터링이에요. 비유로 풀면 — 사무소 곳곳에 단 카메라(JMX)와 온도계(Prometheus) 입니다. Kafka Connect 운영에서 빠지면 안 되는 단계예요.
JMX — 카메라 본체
Kafka Connect Worker는 JMX(Java Management Extensions) 를 통해 메트릭을 노출합니다. JVM 위에서 도는 모든 지표를 외부에서 들여다볼 수 있게 해주는 표준 창문이에요.
# JMX 활성화 환경변수
export JMX_PORT=9999
export KAFKA_JMX_OPTS="-Dcom.sun.management.jmxremote \
-Dcom.sun.management.jmxremote.port=9999 \
-Dcom.sun.management.jmxremote.authenticate=false \
-Dcom.sun.management.jmxremote.ssl=false"
# Connect 시작
bin/connect-distributed.sh config/connect-distributed.properties
주요 JMX 메트릭은 이래요.
kafka.connect:type=connector-task-metrics,connector="my-connector",task="0"
- source-record-poll-total: 총 폴링된 레코드 수
- source-record-write-total: Kafka에 쓴 레코드 수
- batch-size-max: 최대 배치 크기
- poll-batch-avg-time-ms: 평균 폴링 시간
kafka.connect:type=sink-task-metrics,connector="my-connector",task="0"
- sink-record-read-total: Kafka에서 읽은 레코드 수
- sink-record-send-total: 싱크에 보낸 레코드 수
- put-batch-max-time-ms: 최대 배치 처리 시간
비유로 풀면 — 카메라 화면에 케이블 줄(Task) 별로 "오늘 몇 건 받았는가, 몇 건 내보냈는가, 평균 처리 시간이 얼마인가" 가 실시간으로 뜨는 거예요. 이걸 안 보면 어디가 막혔는지 알 수 없죠.
Prometheus + Grafana — 온도계와 차트
JMX 메트릭을 그대로 쓰기는 불편해요. Prometheus가 일정 주기로 JMX를 긁어 가서 시계열 데이터로 저장하고, Grafana가 그걸 차트로 그려 줍니다. 비유로 풀면 — 카메라 영상을 저장해 두는 녹화기(Prometheus) 와 차트로 보여주는 모니터(Grafana) 예요.
# jmx_exporter_config.yml
rules:
- pattern: 'kafka.connect<type=connector-task-metrics, connector=(.+), task=(\d+)><>source-record-poll-total'
name: kafka_connect_source_record_poll_total
labels:
connector: '$1'
task: '$2'
- pattern: 'kafka.connect<type=sink-task-metrics, connector=(.+), task=(\d+)><>sink-record-read-total'
name: kafka_connect_sink_record_read_total
labels:
connector: '$1'
task: '$2'
이렇게 정의해 두면 JMX Exporter가 JMX 지표를 Prometheus 포맷으로 바꿔 줘요. Prometheus가 그걸 긁어 가고, Grafana 대시보드에서 본격 차트로 보게 됩니다.
REST API 기반 헬스체크
#!/bin/bash
CONNECT_URL="http://localhost:8083"
connectors=$(curl -s $CONNECT_URL/connectors | jq -r '.[]')
echo "=== Kafka Connect Status ==="
for connector in $connectors; do
status=$(curl -s $CONNECT_URL/connectors/$connector/status | jq -r '.connector.state')
echo "$connector: $status"
if [ "$status" = "FAILED" ]; then
echo " WARNING: $connector is FAILED!"
# 슬랙·이메일 알림 로직
fi
done
JMX 지표 외에 REST API의 /connectors/.../status 도 핵심 헬스체크 창구예요. 정기적으로 폴링해서 FAILED 상태면 알림을 보내는 식. 이건 cron이나 k8s liveness probe로 자동화하면 됩니다.
여기서 시험 함정이 하나 있어요. "JMX만 보면 충분하지 않나?"라는 단순화는 부족합니다. JMX는 양적 지표(처리량·지연), REST API는 상태 지표(RUNNING·FAILED·PAUSED) — 이 둘을 같이 봐야 진짜 그림이 나와요. 카메라랑 출입대장을 같이 봐야 하는 거예요.
Dead Letter Queue — 고장난 케이블이 떨어뜨린 자료를 따로 모아 두는 박스
데이터 파이프라인을 운영하다 보면 반드시 깨진 메시지가 흘러옵니다. 스키마가 안 맞거나, JSON 파싱이 실패하거나, 필수 필드가 빠지거나. Kafka Connect 운영에서 이걸 어떻게 다루느냐가 핵심 차이를 만들어요.
비유로 풀면 — 케이블이 자료를 옮기다가 일부가 손상된 채로 떨어졌다고 칩시다. 손상된 자료까지 그대로 받아서 처리하면 사무소 전체가 멈춰요. 그래서 고장난 케이블이 떨어뜨린 자료를 따로 모아 두는 박스를 둡니다. 이게 Dead Letter Queue(DLQ) 예요.
errors.tolerance — 깨진 데이터 허용 정책
# 기본값: 오류 발생 시 커넥터 중지
errors.tolerance=none
# 모든 오류를 무시하고 계속 진행
errors.tolerance=all
errors.tolerance=none은 하나라도 깨지면 전체 라인 정지예요. 안전하지만 가동률이 떨어져요. errors.tolerance=all은 깨진 메시지를 건너뛰고 계속합니다. 가동률은 좋지만 그냥 흘려버리면 데이터가 사라져요.
답은 둘 다 아니에요. errors.tolerance=all을 켜되, 깨진 메시지는 DLQ 박스로 보내는 것이 정석입니다.
DLQ 설정
# Sink Connector 설정
errors.tolerance=all
errors.deadletterqueue.topic.name=my-connector-dlq
errors.deadletterqueue.topic.replication.factor=3
errors.deadletterqueue.context.headers.enable=true
errors.log.enable=true
errors.log.include.messages=true
errors.deadletterqueue.topic.name에 지정한 토픽이 고장난 자료 박스예요. 깨진 메시지가 들어오면 원본을 그대로 그 토픽에 넣고, 헤더에 오류 정보를 첨부합니다.
DLQ 메시지 구조
DLQ 토픽 메시지 헤더:
__connect.errors.topic: "source-topic"
__connect.errors.partition: 0
__connect.errors.offset: 12345
__connect.errors.connector.name: "my-sink-connector"
__connect.errors.exception.class.name: "org.apache.kafka.connect.errors.DataException"
__connect.errors.exception.message: "Failed to deserialize data..."
원본 메시지 본문도 그대로 들어 있음
헤더 정보를 보면 어느 토픽의 몇 번째 파티션 몇 번 오프셋에서 무슨 예외가 났는지가 다 적혀 있어요. 운영팀이 이걸 별도 컨슈머로 읽어서 재처리하거나, 알림을 띄우거나, 패치 후 재투입할 수 있습니다.
여기서 시험 함정이 하나 있어요. "errors.tolerance=all만 켜면 끝나는 거 아닌가?"라는 단순화는 데이터 유실을 부릅니다. DLQ 토픽 이름을 같이 지정하지 않으면 깨진 메시지는 그냥 사라져요. 이게 운영팀에서 가장 자주 보는 실수입니다. 두 옵션은 항상 세트로 켜세요.
재시도 설정
retry.backoff.ms=1000
max.retries=10
깨진 메시지로 분류하기 전에 여러 번 재시도해 볼 수도 있어요. 일시적 네트워크 장애 같은 건 보통 재시도로 해결되거든요. 1초 간격으로 10번 재시도 후 그래도 실패하면 DLQ 박스로 보내는 식.
> 한 줄 정리 — Dead Letter Queue = "고장난 케이블이 떨어뜨린 자료를 따로 모아 두는 박스" / errors.tolerance=all + errors.deadletterqueue.topic.name 두 옵션을 세트로.
성능 튜닝 — 케이블 굵기·일꾼 수·교대 주기 조절
이제 Kafka Connect 운영의 마지막 큰 묶음, 성능 튜닝이에요. 비유로 풀면 — 케이블 굵기(batch.size)·일꾼 수(tasks.max)·교대 주기(offset.flush.interval.ms) 세 가지를 상황에 맞게 조절하는 작업입니다.
tasks.max — Task 수 최적화
# Sink Connector: 토픽 파티션 수에 맞춤
# 토픽이 6개 파티션인 경우
tasks.max=6
# Source Connector: 소스의 병렬 처리 단위에 맞춤
# 데이터베이스 테이블 5개를 읽는 경우
tasks.max=5
Sink는 토픽 파티션 수에 맞추는 게 정석입니다. Kafka 컨슈머는 한 파티션을 한 컨슈머가 잡는 구조라, Task 수가 파티션 수보다 많으면 노는 Task가 생겨요. Source는 외부 시스템의 자연스러운 병렬 단위(테이블 수, 파일 수, 셰드 수)에 맞춥니다.
여기서 시험 함정이 하나 있어요. "Task를 무조건 많이 두면 빨라지지 않나?"라는 단순화는 안 통합니다. Task가 파티션 수를 넘으면 그 이상은 노는 자리예요. 일꾼만 늘었지 일거리가 분배되지 않아요. 파티션 수와 동일하게가 정답.
batch.size·linger.ms — 케이블 굵기
# Sink Connector 배치 설정
consumer.max.poll.records=500 # Kafka Consumer 배치 크기
batch.size=1000 # Sink에 보내는 배치 크기
# Source Connector 배치 설정
batch.size=100
배치가 크면 처리량(throughput) 증가, 작으면 지연(latency) 감소라는 트레이드오프가 있어요. 비유로 풀면 — 케이블이 굵으면 한 번에 많이 보내지만 첫 자료가 도착하기까지 오래 기다려야 하고, 가늘면 자주 빨리 보내지만 총량이 적어요. 고처리량 환경은 batch.size를 키우고, 실시간성이 중요한 환경은 줄이는 식.
offset.flush.interval.ms — 교대 주기
# 기본값 (60초)
offset.flush.interval.ms=60000
# 고가용성 요구 시 (10초) — 장애 복구 빠름, 처리량 감소
offset.flush.interval.ms=10000
# 고처리량 요구 시 (5분) — 처리량 증가, 장애 시 재처리 양 많음
offset.flush.interval.ms=300000
오프셋을 자주 저장하면 장애 복구가 빠릅니다(잃어버릴 진행 상황이 적으니까). 대신 저장 자체가 부하라 처리량이 떨어져요. 반대로 덜 자주 저장하면 처리량은 좋지만 장애 시 재처리해야 할 양이 많아져요. 비유로 풀면 — 일꾼들이 자주 진행 상황을 보고하면 안전하지만 본업이 자주 끊기고, 드물게 보고하면 본업은 잘 돌지만 사고 나면 다시 해야 할 일이 많아지는 거예요.
컨버터 성능
| 컨버터 | 처리 속도 | 크기 | 스키마 지원 |
|---|---|---|---|
| JSON Converter | 보통 | 큼 | 선택적 |
| Avro Converter | 빠름 | 작음 | 필수 (Schema Registry) |
| String Converter | 가장 빠름 | 가장 작음 | 없음 |
프로덕션 고처리량 환경은 Avro Converter 가 정석이에요. 메시지 크기가 작아 네트워크 부담이 줄고, 스키마 레지스트리와 결합하면 스키마 진화도 안전하게 됩니다.
Heap 메모리
# Connect Worker JVM 힙 설정
export KAFKA_HEAP_OPTS="-Xms2G -Xmx4G"
# 배치가 크거나 Task가 많은 경우
export KAFKA_HEAP_OPTS="-Xms4G -Xmx8G"
Worker JVM의 힙은 기본값에 머물면 OOM이 잘 납니다. 일거리 양에 맞춰 늘려 주세요. 비유로 풀면 — 일꾼의 책상 크기예요. 책상이 작으면 한 번에 많은 일을 못 합니다.
Sink별 최적화 예시
Elasticsearch Sink:
batch.size=5000 # 배치 크기 증가
linger.ms=1000 # 최대 버퍼링 시간
max.in.flight.requests.per.connection=5 # 동시 요청 수
flush.synchronously=false # 비동기 플러시
JDBC Sink:
batch.max.rows=3000 # 배치 삽입 행 수
insert.mode=upsert # INSERT + UPDATE 결합
auto.commit=false # 배치 완료 시 커밋
connection.pool.max.size=10 # 연결 풀 크기
여기서 시험 함정이 하나 있어요. "성능 튜닝 옵션을 한 번에 다 켜면 빨라지지 않나?"라는 시도는 위험합니다. 튜닝은 한 번에 한 변수씩 바꾸고 측정하는 게 원칙이에요. 다 같이 바꾸면 어떤 게 효과를 냈는지 알 수 없고, 부작용이 어디서 났는지도 모릅니다. JMX 지표를 같이 보면서 한 번에 한 변수씩.
> 한 줄 정리 — 성능 튜닝 = "케이블 굵기·일꾼 수·교대 주기 조절" / tasks.max=파티션 수, batch.size는 처리량과 지연 트레이드오프, offset.flush.interval.ms는 가용성과 처리량 트레이드오프.
보안 — 출입증·자물쇠·열쇠 분배
운영 단계에 오면 보안이 더 이상 미룰 수 없는 작업이에요. Kafka Connect 운영에서 보안은 세 층으로 봅니다.
SSL/TLS — 케이블 자체 암호화
# 카프카 클러스터 보안 연결
security.protocol=SSL
ssl.truststore.location=/path/to/kafka.client.truststore.jks
ssl.truststore.password=truststore-password
ssl.keystore.location=/path/to/kafka.client.keystore.jks
ssl.keystore.password=keystore-password
ssl.key.password=key-password
비유로 풀면 — 케이블 자체에 절연 피복을 입히는 작업입니다. 중간에서 누가 엿들어도 내용이 보이지 않게 해요. truststore는 "이 카프카 클러스터를 믿는다"는 인증서, keystore는 "내가 누구인지 증명하는" 인증서예요.
SASL — 일꾼 신원 확인
# SASL 인증 (SCRAM-SHA-512)
security.protocol=SASL_SSL
sasl.mechanism=SCRAM-SHA-512
sasl.jaas.config=org.apache.kafka.common.security.scram.ScramLoginModule required \
username="connect-user" \
password="connect-password";
비유로 풀면 — 일꾼이 사무소에 들어올 때 출입증을 보여주는 단계예요. 카프카 클러스터에 "나는 connect-user다"라고 신원을 증명합니다. SCRAM은 일종의 비밀번호 기반 인증, GSSAPI는 케르베로스 기반 인증.
ACL — 권한 분배
권한 정책 예시:
- connect-user는 connect-offsets, connect-configs, connect-status 토픽에 읽기·쓰기 권한
- connect-user는 자신의 group.id로 그룹에 합류할 권한
- connect-user는 데이터 토픽에 대한 적절한 권한 (Source면 쓰기, Sink면 읽기)
ACL(Access Control List)은 인증된 일꾼이 무엇을 할 수 있는지 정해 두는 단계예요. SASL이 신원 확인이라면, ACL은 그 신원에 부여된 권한 목록입니다.
REST API 보안
# HTTPS 활성화
listeners=HTTPS://0.0.0.0:8083
ssl.keystore.location=/path/to/keystore.jks
ssl.keystore.password=password
ssl.key.password=key-password
# Basic Auth
rest.extension.classes=org.apache.kafka.connect.rest.basic.auth.extension.BasicAuthSecurityRestExtension
REST API도 외부에 그대로 열어 두면 안 돼요. HTTPS + 기본 인증 또는 사내 게이트웨이 뒤에 두는 게 정석입니다.
민감 정보 보호
# Vault 같은 외부 시크릿 관리 사용
config.providers=vault
config.providers.vault.class=com.github.jcustenborder.kafka.config.vault.VaultConfigProvider
config.providers.vault.param.vault.address=http://vault:8200
config.providers.vault.param.vault.token=my-vault-token
# 커넥터 설정에서 참조
connection.password=${vault:secret/kafka-connect:db-password}
DB 비밀번호 같은 민감 정보는 설정 파일에 평문으로 두면 절대 안 돼요. Vault·AWS Secrets Manager 같은 외부 시크릿 저장소에 넣고, 커넥터 설정에선 ${vault:...} 같은 참조 표현식으로 끌어다 씁니다.
여기서 시험 함정이 하나 있어요. "내부망이니까 SSL 안 써도 되지 않나?"라는 단순화는 위험합니다. 내부망에서도 측면 이동(lateral movement) 공격이 흔해요. 데이터 파이프라인이 흐르는 길은 외부망·내부망 구분 없이 암호화하는 게 현대 운영의 기본입니다.
CI/CD 흐름 — 케이블 설치 자동화
마지막 큰 묶음이 CI/CD입니다. 비유로 풀면 — 케이블 설치를 자동 배송 트럭으로 처리하는 작업이에요. Kafka Connect 운영에서 사람이 손으로 cURL을 치는 시대는 지났습니다.
커넥터 자동 배포 스크립트
#!/bin/bash
# deploy-connector.sh
CONNECT_URL="http://localhost:8083"
CONNECTOR_CONFIG_FILE="$1"
CONNECTOR_NAME=$(jq -r '.name' $CONNECTOR_CONFIG_FILE)
echo "Deploying connector: $CONNECTOR_NAME"
EXISTING=$(curl -s $CONNECT_URL/connectors | jq -r ".[] | select(. == \"$CONNECTOR_NAME\")")
if [ -n "$EXISTING" ]; then
echo "Updating existing connector: $CONNECTOR_NAME"
CONFIG=$(jq '.config' $CONNECTOR_CONFIG_FILE)
curl -s -X PUT \
-H "Content-Type: application/json" \
-d "$CONFIG" \
$CONNECT_URL/connectors/$CONNECTOR_NAME/config
else
echo "Creating new connector: $CONNECTOR_NAME"
curl -s -X POST \
-H "Content-Type: application/json" \
-d @$CONNECTOR_CONFIG_FILE \
$CONNECT_URL/connectors
fi
sleep 3
STATUS=$(curl -s $CONNECT_URL/connectors/$CONNECTOR_NAME/status | jq -r '.connector.state')
echo "Connector status: $STATUS"
if [ "$STATUS" = "RUNNING" ]; then
echo "Connector deployed successfully"
exit 0
else
echo "Connector deployment failed"
exit 1
fi
이 스크립트가 CI/CD 파이프라인의 한 단계가 돼요. Git에 커넥터 설정을 푸시하면 GitHub Actions(또는 GitLab CI)가 이 스크립트를 호출해 자동 배포합니다.
환경별 설정 분리
권장 디렉터리 구조:
connectors/
├── dev/
│ ├── source-twitter.json
│ └── sink-elasticsearch.json
├── staging/
│ ├── source-twitter.json
│ └── sink-elasticsearch.json
└── prod/
├── source-twitter.json
└── sink-elasticsearch.json
같은 커넥터라도 dev·staging·prod의 브로커 주소·인증·tasks.max가 달라요. 디렉터리로 명확하게 분리해 두는 게 운영팀 표준입니다.
커넥터 업데이트 — pause·update·resume
# 1. 현재 설정 백업
curl -s http://localhost:8083/connectors/my-connector/config \
> backup-config.json
# 2. 커넥터 일시 정지
curl -s -X PUT \
http://localhost:8083/connectors/my-connector/pause
# 3. 설정 업데이트
curl -s -X PUT \
-H "Content-Type: application/json" \
-d @new-config.json \
http://localhost:8083/connectors/my-connector/config
# 4. 커넥터 재개
curl -s -X PUT \
http://localhost:8083/connectors/my-connector/resume
설정 변경은 백업 → pause → update → resume 4단계가 정석. 비유로 풀면 — 케이블을 교체하기 전에 일을 잠깐 멈추고, 새 케이블로 갈아 끼운 뒤 다시 일을 시작하는 거예요. 갑자기 바꾸면 진행 중이던 작업이 어디까지 갔는지 헷갈립니다.
여기서 시험 함정이 하나 있어요. "config만 PUT으로 보내면 자동으로 재시작되지 않나?"라는 단순화는 부분적으로 맞지만 위험합니다. PUT으로 config를 바꾸면 Connect는 자동으로 Task를 재시작해요. 그러나 진행 중이던 트랜잭션이 어떻게 끊길지 보장이 없어요. 명시적으로 pause → update → resume이 더 안전합니다.
모범 사례 — 운영팀이 자주 부르는 룰들
여기까지 흐름을 다 따라왔다면 Kafka Connect 운영의 큰 그림은 들어왔어요. 운영팀이 자주 부르는 룰을 모아 둡니다.
프로덕션 배포 전 확인:
[ ] Distributed Mode 사용
[ ] replication.factor=3 (내부 토픽)
[ ] Worker 최소 3대
[ ] 커넥터 설정 파일 Git 관리
[ ] 민감한 정보는 Secrets Manager 사용
[ ] 모니터링 설정 (JMX·Prometheus·Grafana)
[ ] 에러 허용 + DLQ 구성
[ ] tasks.max는 파티션 수에 맞춤
[ ] 컨버터·schemas.enable 결정
[ ] plugin.path에 커넥터 JAR 경로 설정
[ ] SSL/SASL·ACL 설정
[ ] CI/CD로 커넥터 배포 자동화
[ ] Rolling restart 절차 문서화
[ ] DLQ 알림·대시보드 구성
Remember Me — 핵심 설정 셋
연결의 핵심 설정:
Source Connector:
connector.class = [클래스명]
tasks.max = [숫자]
topic = [쓸 토픽]
Sink Connector:
connector.class = [클래스명]
tasks.max = [파티션 수]
topics = [읽을 토픽]
모든 커넥터 공통:
key.converter = [컨버터 클래스]
value.converter = [컨버터 클래스]
key.converter.schemas.enable = true/false
value.converter.schemas.enable = true/false
이 설정 셋만 외워 두면 어디 가서도 커넥터를 돌릴 수 있어요.
다음 학습 방향 — Kafka 생태계 확장
Kafka Connect 시리즈는 여기서 마무리되지만, Kafka 생태계는 더 넓어요. 다음에 어디로 가면 좋을지 짧게 정리합니다.
| 주제 | 설명 | 추천 이유 |
|---|---|---|
| Kafka Streams API | 실시간 스트림 처리 | Connect의 Transform 부분 보완 |
| ksqlDB | SQL로 스트림 처리 | Streams보다 진입 장벽 낮음 |
| Schema Registry + Avro | 스키마 관리 | 프로덕션 거의 필수 |
| Kafka Security | TLS·SASL·ACL 심화 | 본격 운영 필수 |
| Kafka 운영 | 클러스터 자체 운영 | Connect 안정성의 토대 |
특히 Kafka Streams는 1편에서 본 ETL의 Transform 자리를 메우는 도구예요. Connect가 Extract·Load만 책임진다면, Streams는 그 사이의 Transform을 본격적으로 담당합니다. ksqlDB는 Streams를 SQL로 쓸 수 있게 해주는 도구라 운영팀에서 진입 장벽이 낮습니다.
Schema Registry는 4편에서 SMT를 다룰 때 잠깐 봤듯이, Avro 컨버터와 짝이 되는 중앙 스키마 관리 서비스예요. 메시지 크기를 줄이고 스키마 진화를 안전하게 관리하려면 거의 필수입니다.
Kafka Connect 운영 — 시리즈 전체 마무리
여기까지가 Apache Kafka Connect 실전 정리 시리즈 5편 전체의 큰 그림입니다. 한 번 정리하고 갈게요.
- 1편 — 표준 케이블 비유로 큰 그림 — Connector·Task·Worker 3층 구조, Standalone vs Distributed, Confluent Hub
- 2편 — Source·Sink 커넥터 종류와 설정 — JDBC·Twitter·MongoDB·Elasticsearch·S3 등 실습
- 3편 — Distributed Mode·REST API — 본격 사무소 차리기, REST API로 커넥터 배포
- 4편 — SMT·커스텀 커넥터 — 데이터 가공, 필요한 케이블이 없을 때 직접 만들기
- 5편(이번 글) — Kafka Connect 운영·프로덕션 배포 — 본사 빌딩 운영, 모니터링, DLQ, 튜닝, 보안, CI/CD
각 편이 다음 편의 토대가 되도록 비유를 쌓아 왔어요. 1편의 표준 케이블이 2편에서 실제 부품이 되고, 3편에서 사무소가 차려지고, 4편에서 가공 라인이 붙고, 5편에서 본사 운영 단계로 옮겨 가는 흐름입니다.
Kafka Connect 운영 — 압축 노트 (시험 직전·실무 현장용)
마지막 압축 노트예요. 시험 직전 또는 실무에서 헷갈릴 때 다시 펼쳐 보시면 됩니다.
- Kafka Connect 운영 = 사무소를 본격 운영 단계로 옮기기 (테스트 원룸 → 본사 빌딩)
- 프로덕션 배포 핵심 — Apache Kafka 직접 다운로드 → connect-distributed.properties → 커넥터 JAR → connect-distributed.sh
- replication.factor=3 — 내부 토픽(connect-offsets·connect-configs·connect-status) 모두
config.storage.partitions는 항상 1, 변경 불가- Worker 최소 3대 — 1·2대는 본질적으로 Standalone과 차이 없음
- 같은 클러스터 합류 조건 —
bootstrap.servers·group.id·내부 토픽 3종·plugin.path동일, REST 포트만 다름 - Rolling restart — 일꾼 한 명씩 교대로 쉬게 하기, 자동 재균형이 백업 일꾼 역할
- 한 번에 한 명만 쉰다 — 동시 다수 다운은 남은 Worker 과부하
- JMX = 카메라, Prometheus = 녹화기, Grafana = 모니터 — 셋이 한 세트
- 주요 JMX 지표 —
source-record-poll-total·sink-record-read-total·poll-batch-avg-time-ms - REST API 헬스체크 —
/connectors/.../status,FAILED면 알림 - Dead Letter Queue = 고장난 자료 박스 —
errors.tolerance=all+errors.deadletterqueue.topic.name세트 - DLQ 메시지 헤더에 토픽·파티션·오프셋·예외 정보 자동 첨부
- tasks.max — Sink는 파티션 수, Source는 자연스러운 병렬 단위(테이블 수 등)
- batch.size — 크면 처리량 증가·지연 증가, 작으면 반대 (트레이드오프)
- offset.flush.interval.ms — 짧으면 가용성 좋음·처리량 감소, 길면 반대
- Avro Converter — 프로덕션 고처리량 환경 정석 (Schema Registry 필요)
- 보안 3층 — SSL(케이블 암호화) + SASL(신원 확인) + ACL(권한 분배)
- 민감 정보 — Vault·AWS Secrets Manager 같은 외부 시크릿 저장소 +
${...}참조 - 커넥터 업데이트 — 백업 → pause → update → resume 4단계
- CI/CD — Git 저장소에 dev·staging·prod 분리, 자동 배포 스크립트
- 한 변수씩 튜닝하고 JMX로 측정, 한꺼번에 다 바꾸지 않기
- 다음 학습 — Kafka Streams(Transform 자리), ksqlDB(SQL 인터페이스), Schema Registry(중앙 스키마 관리)
시리즈 다른 편
같은 시리즈의 다른 글들도 같은 친절 톤으로 묶어 정리되어 있어요.
- 1편 — Kafka Connect 입문
- 2편 — Source · Sink Connector
- 3편 — Distributed Mode · REST API
- 4편 — SMT · 커스텀 커넥터
- 5편 — 운영 · 프로덕션 (완) (현재 글, 완결)
이 시리즈를 마치며
여기까지 다섯 편을 함께 따라와 주신 분들께 진심으로 고마운 마음을 전합니다. Kafka Connect는 처음 보면 부품 이름·아키텍처·설정 옵션이 뒤엉켜 막막해 보이지만, 표준 케이블·일꾼·부품이라는 비유 하나로 풀어 가면 의외로 명료한 도구예요. 1편에서 큰 그림을 잡고, 2~4편에서 실제 손을 움직여 보고, 이번 5편에서 본격 운영까지 — 다섯 편을 모두 거치고 나면 어떤 데이터 파이프라인을 만나도 "아, 이건 Source 커넥터로 빼서 SMT로 가공한 다음 Sink로 받고, DLQ는 이렇게 두면 되겠구나"라는 그림이 자연스럽게 그려질 거예요.
이 시리즈가 시험 준비든 실무 적용이든 한 분의 학습 곡선을 조금이라도 가볍게 해드렸다면 그걸로 충분히 의미가 있다고 생각해요. 어디선가 비슷한 데이터 파이프라인을 운영하면서 "예전에 표준 케이블 비유로 본 적 있지" 하고 한 번 떠올려 주시면 더 바랄 게 없겠습니다. 다음 학습이 어떤 방향이든, 좋은 흐름으로 이어지시길 바라요. 다섯 편 끝까지 함께해 주셔서 정말 고맙습니다.