복사해서 값만 바꾸면 동작하는 설정 스니펫과, 운영자가 가장 많이 틀리는
listeners / advertised.listeners /
listener.security.protocol.map 조합 패턴 5가지를 정리했습니다.
KRaft 기준이며 모든 설정명·기본값은 Apache Kafka 4.3.1 공식 문서에서 확인했습니다.
4계층으로 나눠 생각하세요
암호화 — 전송 구간 TLS. SSL 또는 SASL_SSL.
인증(authentication) — 누구인가. mTLS 인증서 또는 SASL.
인가(authorization) — 무엇을 할 수 있는가. ACL.
감사 — 누가 무엇을 했는가. kafka-authorizer.log.
보안 프로토콜 4종
listener.security.protocol.map에 쓸 수 있는 값은 아래 4개뿐입니다
(대소문자 구분 없음). 브로커의 기본
listener.security.protocol.map 값은
SASL_SSL:SASL_SSL,PLAINTEXT:PLAINTEXT,SSL:SSL,SASL_PLAINTEXT:SASL_PLAINTEXT이므로,
리스너 이름을 프로토콜 이름과 같게 지으면 이 설정을 생략할 수 있습니다.
보안 프로토콜 비교
프로토콜
암호화
인증 주체
Principal 결정
언제 쓰는가
PLAINTEXT
없음
없음 — 누구나 접속
ANONYMOUS
로컬 개발 전용. 운영에서 절대 노출하지 마세요
SSL
TLS
클라이언트 인증서 (mTLS). ssl.client.auth가 required일 때
인증서 DN → ssl.principal.mapping.rules
인증서 관리 체계가 이미 있는 조직
SASL_PLAINTEXT
없음
SASL (SCRAM · GSSAPI 등)
SASL 사용자명
사설망 내부 구간에서만. PLAIN 메커니즘과 조합하면 비밀번호가 평문으로 흐릅니다
SASL_SSL
TLS
SASL
SASL 사용자명
운영 기본 선택. 암호화 + 사용자 단위 인증
SASL 메커니즘 5종 — Apache Kafka 4.3이 지원하는 전부
메커니즘
자격 증명 저장 위치
TLS 필요성
선택 기준
PLAIN
브로커 JAAS 설정 (또는 커스텀 콜백 핸들러)
필수. 공식 문서가 "SSL과만 함께 쓸 것"을 명시합니다
가장 단순하지만 사용자 추가에 브로커 재시작이 필요합니다
SCRAM-SHA-256
메타데이터 로그 (KRaft)
필수 (교환 가로채기 방지)
사용자를 동적으로 추가·변경할 수 있습니다. 외부 IdP가 없을 때의 기본 선택
SCRAM-SHA-512
메타데이터 로그
필수
SHA-256과 동일. 더 강한 해시
GSSAPI (Kerberos)
KDC · keytab
권장
이미 AD/Kerberos를 쓰는 조직. sasl.mechanism의 기본값이 이것이라 실수로 남는 경우가 많습니다
OAUTHBEARER
외부 OAuth 2.0 IdP
필수
기본 구현은 서명 없는 JWT라 비운영 전용입니다. 운영에는 IdP 연동 구현을 씁니다
TLS (SSL) 설정
단방향 TLS — 암호화만, 클라이언트 인증 없음
server.properties — 브로커
process.roles=broker,controller
node.id=1
listeners=CLIENT://:9092,CONTROLLER://:9093
advertised.listeners=CLIENT://kafka-1.example.com:9092
listener.security.protocol.map=CLIENT:SSL,CONTROLLER:SSL
inter.broker.listener.name=CLIENT
controller.listener.names=CONTROLLER
controller.quorum.bootstrap.servers=kafka-1.example.com:9093
# 브로커 자신의 인증서
ssl.keystore.location=/var/private/ssl/server.keystore.jks
ssl.keystore.password=changeit
ssl.key.password=changeit
# 클라이언트 인증서를 요구하지 않습니다 (기본값이 none 입니다)
ssl.client.auth=none
# 브로커 간 통신은 서로의 인증서를 검증하므로 truststore 가 필요합니다
ssl.truststore.location=/var/private/ssl/server.truststore.jks
ssl.truststore.password=changeit
client.properties — 클라이언트
security.protocol=SSL
ssl.truststore.location=/var/private/ssl/client.truststore.jks
ssl.truststore.password=changeit
# 기본값이 https 입니다. 호스트명 검증을 켜 두세요.
# advertised.listeners 의 호스트명이 인증서 SAN 과 일치해야 합니다.
ssl.endpoint.identification.algorithm=https
양방향 TLS (mTLS) — 인증서로 인증까지
server.properties — 위 설정에서 이 부분만 변경
# required: 인증서 없는 클라이언트는 거부됩니다.
# requested 는 공식 문서가 "false sense of security" 라며 권장하지 않습니다
# (잘못 설정된 클라이언트가 그냥 접속됩니다).
ssl.client.auth=required
# 클라이언트 인증서를 서명한 CA 를 truststore 에 넣어야 합니다
ssl.truststore.location=/var/private/ssl/server.truststore.jks
ssl.truststore.password=changeit
# 인증서 DN 을 어떤 Principal 로 매핑할지.
# 기본값 DEFAULT 는 전체 DN 을 그대로 Principal 로 씁니다:
# User:CN=alice,OU=eng,O=example,L=Seoul,ST=Seoul,C=KR
# CN 만 쓰고 싶으면 규칙을 지정합니다.
ssl.principal.mapping.rules=RULE:^CN=(.*?),.*$/$1/,DEFAULT
listeners=CLIENT://:9092,CONTROLLER://:9093
listener.security.protocol.map=CLIENT:SASL_SSL,CONTROLLER:SASL_SSL
inter.broker.listener.name=CLIENT
controller.listener.names=CONTROLLER
sasl.enabled.mechanisms=PLAIN
sasl.mechanism.inter.broker.protocol=PLAIN
# username/password 는 브로커가 "다른 브로커에 접속할 때" 쓰는 자격 증명
# user_{이름}={비밀번호} 는 이 브로커가 "받아들일" 사용자 목록
listener.name.client.plain.sasl.jaas.config=\
org.apache.kafka.common.security.plain.PlainLoginModule required \
username="admin" \
password="admin-secret" \
user_admin="admin-secret" \
user_alice="alice-secret";
리스너 조합 패턴 5가지
운영자가 가장 많이 틀리는 부분입니다. 세 설정의 역할을 먼저 분리하세요.
세 설정의 역할
설정
누가 쓰는가
무엇을 정하는가
listeners
브로커 자신
어디에 바인딩할지. 이름://호스트:포트. 호스트를 비우면 모든 인터페이스
advertised.listeners
클라이언트
메타데이터 응답으로 클라이언트에게 알릴 주소. 클라이언트가 실제로 접속할 주소여야 합니다
listener.security.protocol.map
브로커
리스너 이름 → 보안 프로토콜 매핑. 이름을 프로토콜과 다르게 지었으면 필수
패턴 1 — 단일 리스너 (로컬 개발)
server.properties
process.roles=broker,controller
node.id=1
# 리스너 이름을 프로토콜 이름과 같게 지으면
# listener.security.protocol.map 을 생략할 수 있습니다.
listeners=PLAINTEXT://:9092,CONTROLLER://:9093
listener.security.protocol.map=PLAINTEXT:PLAINTEXT,CONTROLLER:PLAINTEXT
inter.broker.listener.name=PLAINTEXT
controller.listener.names=CONTROLLER
controller.quorum.bootstrap.servers=localhost:9093
# 생략하면 listeners 값이 그대로 광고됩니다.
# localhost 로 접속하는 로컬 개발에서는 이대로도 동작합니다.
advertised.listeners=PLAINTEXT://localhost:9092
패턴 2 — 내부 / 외부 분리 (사내망 + 외부 클라이언트)
server.properties — 두 개의 클라이언트 리스너
process.roles=broker
node.id=1
# INTERNAL: 브로커 간 복제 + 사내 클라이언트 (사설 IP)
# EXTERNAL: 외부 클라이언트 (공인 도메인, 인증 강제)
# CONTROLLER: 컨트롤러 통신 — listeners 에 없어도 반드시 "정의"해야 합니다
listeners=INTERNAL://:9092,EXTERNAL://:9094
advertised.listeners=INTERNAL://kafka-1.internal:9092,EXTERNAL://kafka-1.example.com:9094
listener.security.protocol.map=INTERNAL:SASL_PLAINTEXT,EXTERNAL:SASL_SSL,CONTROLLER:SASL_SSL
inter.broker.listener.name=INTERNAL
controller.listener.names=CONTROLLER
controller.quorum.bootstrap.servers=controller-1.internal:9093,controller-2.internal:9093,controller-3.internal:9093
sasl.enabled.mechanisms=SCRAM-SHA-512
sasl.mechanism.inter.broker.protocol=SCRAM-SHA-512
sasl.mechanism.controller.protocol=SCRAM-SHA-512
패턴 3 — 브로커 전용 노드 (역할 분리)
server.properties — process.roles=broker
process.roles=broker
node.id=11
# 컨트롤러 역할이 없으므로 CONTROLLER 를 listeners 에 넣지 않습니다.
# 브로커가 컨트롤러 리스너를 "노출"하지는 않기 때문입니다.
listeners=BROKER://:9092
advertised.listeners=BROKER://kafka-1.internal:9092
inter.broker.listener.name=BROKER
# 하지만 컨트롤러에 "접속"해야 하므로 리스너 이름과 보안 설정은 정의해야 합니다.
# 포트는 controller.quorum.bootstrap.servers 에서 옵니다.
controller.listener.names=CONTROLLER
controller.quorum.bootstrap.servers=controller-1.internal:9093,controller-2.internal:9093,controller-3.internal:9093
listener.security.protocol.map=BROKER:SASL_SSL,CONTROLLER:SASL_SSL
controller.properties — process.roles=controller
process.roles=controller
node.id=1
# 컨트롤러는 컨트롤러 리스너를 실제로 노출합니다.
listeners=CONTROLLER://:9093
controller.listener.names=CONTROLLER
controller.quorum.bootstrap.servers=controller-1.internal:9093,controller-2.internal:9093,controller-3.internal:9093
listener.security.protocol.map=CONTROLLER:SASL_SSL
# 컨트롤러 전용 노드는 advertised.listeners 를 쓰지 않습니다
# (클라이언트가 붙는 대상이 아닙니다).
패턴 4 — Docker Compose
docker-compose.yml — 컨테이너 내부와 호스트 양쪽에서 접속
services:
kafka-1:
image: apache/kafka:4.3.1
hostname: kafka-1
ports:
- "19092:19092" # 호스트에서 접속할 포트
environment:
KAFKA_PROCESS_ROLES: broker,controller
KAFKA_NODE_ID: 1
# DOCKER : 컨테이너 네트워크 내부에서 서비스명으로 접속 (다른 컨테이너용)
# HOST : 호스트 머신에서 localhost 로 접속 (개발자 노트북용)
# CONTROLLER: 컨트롤러 쿼럼
KAFKA_LISTENERS: DOCKER://:9092,HOST://:19092,CONTROLLER://:9093
KAFKA_ADVERTISED_LISTENERS: DOCKER://kafka-1:9092,HOST://localhost:19092
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: DOCKER:PLAINTEXT,HOST:PLAINTEXT,CONTROLLER:PLAINTEXT
KAFKA_INTER_BROKER_LISTENER_NAME: DOCKER
KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER
KAFKA_CONTROLLER_QUORUM_BOOTSTRAP_SERVERS: kafka-1:9093
# 단일 노드에서는 내부 토픽 RF 를 1 로 낮춰야 기동합니다
# (기본값 3 이라 브로커 1대에서는 InvalidReplicationFactorException)
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1
KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1
패턴 5 — Kubernetes (StatefulSet + 파드별 외부 노출)
브로커 컨테이너 진입 스크립트 — 파드 서수로 주소를 계산
#!/usr/bin/env bash
set -euo pipefail
# StatefulSet 의 파드 이름은 kafka-0, kafka-1, … 형태로 안정적입니다.
ORDINAL="${HOSTNAME##*-}"
NODE_ID=$((ORDINAL + 1))
# INTERNAL : 클러스터 내부 — headless Service 의 안정적 DNS 이름
# EXTERNAL : 클러스터 외부 — 파드별 LoadBalancer/NodePort 도메인
# 파드마다 값이 달라야 하므로 여기서 계산합니다.
INTERNAL_HOST="${HOSTNAME}.kafka-headless.${POD_NAMESPACE}.svc.cluster.local"
EXTERNAL_HOST="kafka-${ORDINAL}.${EXTERNAL_DOMAIN}"
exec /opt/kafka/bin/kafka-server-start.sh /opt/kafka/config/server.properties \
--override node.id="${NODE_ID}" \
--override listeners="INTERNAL://:9092,EXTERNAL://:9094" \
--override advertised.listeners="INTERNAL://${INTERNAL_HOST}:9092,EXTERNAL://${EXTERNAL_HOST}:9094" \
--override listener.security.protocol.map="INTERNAL:SASL_SSL,EXTERNAL:SASL_SSL,CONTROLLER:SASL_SSL" \
--override inter.broker.listener.name="INTERNAL" \
--override controller.listener.names="CONTROLLER" \
--override controller.quorum.bootstrap.servers="kafka-controller-0.kafka-controller-headless.${POD_NAMESPACE}.svc.cluster.local:9093"
ACL
Authorizer 켜기
server.properties — 모든 노드(브로커·컨트롤러·combined)에 적용
# KRaft 는 ACL 을 클러스터 메타데이터(KRaft 메타데이터 로그)에 저장합니다
authorizer.class.name=org.apache.kafka.metadata.authorizer.StandardAuthorizer
# 구분자는 세미콜론입니다 (SSL DN 에 콤마가 들어갈 수 있으므로).
# PrincipalType 문자열 "User" 는 대소문자를 구분합니다.
super.users=User:admin;User:kafka-operator
# ACL 이 없는 리소스는 기본적으로 거부됩니다 (super user 만 접근 가능).
# 아래를 true 로 두면 ACL 이 "하나도 없는" 리소스만 모두에게 열립니다.
# ACL 이 하나라도 있으면 이 설정과 무관하게 ACL 이 적용됩니다.
#allow.everyone.if.no.acl.found=true
자주 쓰는 ACL 명령
편의 옵션 — 이것부터 쓰세요
export BS=localhost:9092
CC="--command-config admin.properties"
# 프로듀서 역할: 토픽에 WRITE, DESCRIBE, CREATE 를 한 번에 부여
bin/kafka-acls.sh --bootstrap-server $BS $CC --add \
--allow-principal User:Bob --producer --topic orders
# 멱등 프로듀서라면 --idempotent 를 함께 (Cluster IdempotentWrite)
bin/kafka-acls.sh --bootstrap-server $BS $CC --add \
--allow-principal User:Bob --producer --idempotent --topic orders
# 컨슈머 역할: 토픽에 READ·DESCRIBE + 그룹에 READ
# 그룹 권한을 빼먹는 것이 가장 흔한 실수입니다
bin/kafka-acls.sh --bootstrap-server $BS $CC --add \
--allow-principal User:Bob --consumer --topic orders --group order-processor
# 접두어 기반 — 토픽이 늘어나도 재부여가 필요 없습니다
bin/kafka-acls.sh --bootstrap-server $BS $CC --add \
--allow-principal User:Jane --producer \
--topic "order-" --resource-pattern-type prefixed
# 조회
bin/kafka-acls.sh --bootstrap-server $BS $CC --list --topic orders
# 특정 토픽에 매칭되는 모든 패턴(literal + prefixed + wildcard)을 함께 보기
bin/kafka-acls.sh --bootstrap-server $BS $CC --list \
--topic orders --resource-pattern-type match
# 제거 — 부여할 때와 같은 인자로
bin/kafka-acls.sh --bootstrap-server $BS $CC --remove \
--allow-principal User:Bob --producer --topic orders --force
세밀한 제어 — 오퍼레이션을 직접 지정
# 여러 principal · 여러 host · 여러 operation 을 한 번에
bin/kafka-acls.sh --bootstrap-server $BS $CC --add \
--allow-principal User:Bob --allow-principal User:Alice \
--allow-host 198.51.100.0 --allow-host 198.51.100.1 \
--operation Read --operation Write \
--topic orders
# Deny 는 Allow 를 이깁니다. 특정 principal 만 차단할 때 씁니다.
bin/kafka-acls.sh --bootstrap-server $BS $CC --add \
--allow-principal User:'*' --allow-host '*' \
--deny-principal User:BadBob --deny-host 198.51.100.3 \
--operation Read --topic orders
# 트랜잭션 프로듀서
bin/kafka-acls.sh --bootstrap-server $BS $CC --add \
--allow-principal User:Bob \
--operation Write --operation Describe \
--transactional-id order-tx-
# 클러스터 레벨 (재할당·브로커 설정 변경 등)
bin/kafka-acls.sh --bootstrap-server $BS $CC --add \
--allow-principal User:kafka-operator \
--operation Alter --operation ClusterAction --cluster
오퍼레이션 13종 · 리소스 타입 6종
ACL 하나는 (principal, host, operation, resource, permission) 조합입니다.
--operation에 쓸 수 있는 값과 리소스 타입은 아래가 전부입니다.
--operation 유효값 13종 — 공식 kafka-acls.sh 문서 기준
Operation
대표 용도
Read
fetch, 오프셋 커밋, 그룹 조인·하트비트
Write
produce, 트랜잭션 API
Create
토픽 생성 (자동 생성 포함)
Delete
토픽 삭제, 레코드 삭제
Alter
파티션 추가, 재할당, ACL 변경
Describe
메타데이터·오프셋·그룹 조회. 없으면 리소스가 존재하지 않는 것처럼 보입니다
ClusterAction
브로커 간 동작(복제 fetch 등). 브로커 principal에 필요
DescribeConfigs
kafka-configs --describe
AlterConfigs
kafka-configs --alter
IdempotentWrite
멱등 프로듀서 (Cluster 리소스에 부여)
CreateTokens
위임 토큰 생성
DescribeTokens
위임 토큰 조회
All
위 전부. 편하지만 최소 권한 원칙에 반합니다
리소스 타입 6종 — ResourceType.java 확인 (UNKNOWN·ANY 제외)
리소스
kafka-acls.sh 옵션
메모
TOPIC
--topic
가장 흔합니다
GROUP
--group
컨슈머 그룹. 토픽 권한과 별개로 반드시 필요
CLUSTER
--cluster
단일 리소스. 클러스터 전역 동작
TRANSACTIONAL_ID
--transactional-id
*는 모든 트랜잭션 ID
DELEGATION_TOKEN
--delegation-token
*는 모든 토큰
USER
--user-principal
현재는 위임 토큰과 연계해 쓰입니다
--resource-pattern-type — 패턴 매칭 방식
값
의미
언제 쓰는가
literal
이름이 정확히 일치. *는 전체를 의미하는 특수값
기본. 개별 리소스
prefixed
주어진 문자열로 시작하는 모든 리소스
팀·서비스별 네임스페이스. 토픽이 늘어나도 재부여 불필요
match
주어진 이름에 매칭되는 모든 패턴을 찾습니다 (조회 전용)
--list로 "이 토픽에 걸린 ACL 전부"를 볼 때
any
모든 패턴 타입 (조회·삭제 필터)
--list · --remove
역할별 필요 권한
역할별 필요 ACL — 공식 문서의 오퍼레이션·리소스 매핑 기준
하려는 일
Operation
Resource
메모
일반 produce
Write
Topic
멱등 produce
IdempotentWrite
Cluster
4.x는 멱등성이 기본 true
트랜잭션 produce
Write
TransactionalId
transactional.id를 설정한 프로듀서
일반 consume (fetch)
Read
Topic
파티션마다 필요합니다
오프셋 커밋
Read
Group + Topic
둘 다 필요합니다. 그룹 권한 누락이 가장 흔한 실수
오프셋 조회
Describe
Group + Topic
그룹 조인 · 하트비트 · 리브 · 동기화
Read
Group
JOIN_GROUP / HEARTBEAT / LEAVE_GROUP / SYNC_GROUP
그룹 상세 조회
Describe
Group
그룹 목록 조회
Describe
Cluster 또는 Group
클러스터 권한이 없으면 권한 있는 그룹만 반환됩니다
코디네이터 찾기 (컨슈머)
Describe
Group
FIND_COORDINATOR
코디네이터 찾기 (트랜잭션)
Describe
TransactionalId
메타데이터 조회
Describe
Topic
이것이 없으면 토픽이 존재하지 않는 것처럼 보입니다
토픽 자동 생성
Create
Cluster 또는 Topic
클러스터 권한을 먼저 확인하고, 없으면 토픽 레벨로 폴백합니다
토픽 생성 (명시)
Create
Cluster 또는 Topic
클러스터 권한이 없으면 CLUSTER_AUTHORIZATION_FAILED가 아니라 토픽 권한으로 폴백합니다
설정명·기본값·명령 형태는 Apache Kafka 4.3.1 공식 문서에서 확인했습니다.
super.users와 allow.everyone.if.no.acl.found의 기본 동작은
설정 표에 없어 StandardAuthorizer 소스로 확인했습니다.
리스너 패턴 4·5(Docker · Kubernetes)의 구성은 공식 문서의 설정 규칙을 적용한
이 가이드의 구성이며, 공식 문서가 제시한 예시가 아닙니다.