현대 시스템 디자인에서 이벤트 주도 아키텍처 (EDA)는 시스템 내에서 이벤트를 생성, 감지, 사용하고 응답하는 데 초점을 맞춥니다. 이벤트는 사용자 조치, 상태 변경 또는 데이터 업데이트와 같이 시스템의 하드웨어 또는 소프트웨어에 영향을 줄 수 있는 중요한 사건들입니다.
EDA는 응용 프로그램의 다양한 부분이 이벤트를 통해 직접 호출 대신 통신할 수 있도록 해 결합을 느슨하게 합니다. 이러한 설정을 통해 구성 요소들이 독립적으로 작동하고 이벤트에 비동기적으로 응답하며, 비즈니스 요구 사항의 변경에 대응하여 주요 시스템 재구성 없이 민첩성을 촉진합니다.
새로운 현대 응용 프로그램들은 이제 실시간 데이터 처리와 반응에 크게 의존합니다. EDA의 중요성은 이러한 요구 사항을 지원하는 프레임워크를 제공하기 때문에 과소 평가될 수 없습니다. 비동기 통신과 이벤트 주도 상호작용을 사용함으로써 시스템은 높은 거래량을 효율적으로 처리하고 불안정한 부하 하에서 성능을 유지할 수 있습니다. 이러한 기능들은 변화가 매우 순간적인 환경에서 특히 감사받습니다, 예를 들어 전자 상거래 플랫폼이나 IoT 애플리케이션과 같은 환경에서.
EDA의 주요 구성 요소 중 일부는 다음과 같습니다:
-
이벤트 소스: 시스템 내에서 중요한 동작이 발생할 때 이벤트를 생성하는 프로듀서들입니다. 사용자 상호작용 또는 데이터 변경과 같은 예시가 있습니다.
-
리스너: 특정 이벤트에 구독하고 해당 이벤트가 발생할 때 응답하는 개체들입니다. 리스너는 시스템이 변화에 동적으로 반응할 수 있도록 합니다.
-
핸들러: 해당 이벤트가 리스너에 의해 감지되면 이벤트를 처리하는 역할을 맡고, 이벤트에 의해 트리거된 필요한 비즈니스 로직이나 워크플로를 실행합니다.
이 기사에서는 Traefik, Kafka 및 Docker를 사용하여 이벤트 주도 데이터 처리를 구현하는 방법을 배우게 됩니다.
여기에는 오늘 구축할 내용을 간략히 보여줄 수 있는 GitHub에 호스팅된 간단한 애플리케이션이 있습니다.
목차
다룰 내용은 다음과 같습니다:
시작해 봅시다!
필수 조건
시작하기 전에:
-
최소 4GB RAM 및 20GB의 여유 디스크 공간을 갖춘 Ubuntu 24.04 인스턴스를 배포하여 Docker 이미지, 컨테이너 및 Kafka 데이터를 수용합니다.
-
sudo 권한이 있는 비 root 사용자로 인스턴스에 액세스합니다.
-
패키지 인덱스를 업데이트합니다.
sudo apt update
기술 이해
아파치 카프카
아파치 카프카는 고처리량 데이터 파이프라인 및 실시간 스트리밍 애플리케이션용으로 만들어진 분산 이벤트 스트리밍 플랫폼입니다. 대량의 이벤트를 효율적으로 처리하여 EDA를 구현하는 백본 역할을 합니다. Kafka는 프로듀서가 이벤트를 토픽에 보내고, 소비자가 해당 토픽을 구독하여 이벤트를 수신하는 발행-구독 모델을 사용합니다.
카프카의 주요 기능 중 일부는 다음과 같습니다:
-
고처리량: Kafka는 낮은 지연 시간으로 초당 수백만 건의 이벤트를 처리할 수 있어 고용량 응용 프로그램에 적합합니다.
-
고장 허용성: Kafka의 분산 아키텍처는 서버 장애 상황에서도 데이터의 내구성과 가용성을 보장합니다. 클러스터 내에서 데이터를 여러 브로커에 복제합니다.
-
확장성: Kafka는 클러스터에 브로커를 추가하거나 주제에 파티션을 더해 수평적으로 쉽게 확장할 수 있어, 성장하는 데이터 요구를 중요한 재구성 없이 수용할 수 있습니다.
Traefik
Traefik은 마이크로서비스 아키텍처를 위해 특별히 설계된 현대적인 HTTP 역방향 프록시 및 로드 밸런서입니다. 인프라에서 실행 중인 서비스를 자동으로 발견하고 트래픽을 이에 맞게 라우팅합니다. Traefik은 서비스 메타데이터에 기반한 동적 라우팅 기능을 제공해 마이크로서비스의 관리를 간단하게 합니다.
Traefik의 주요 기능 중 일부는 다음과 같습니다:
-
동적 구성: Traefik은 서비스가 추가되거나 제거될 때 자동으로 라우팅 구성을 업데이트하여 수동 개입을 없앱니다.
-
로드 밸런싱: 여러 서비스 인스턴스 간에 수신 요청을 효율적으로 분배하여 성능과 신뢰성을 향상시킵니다.
-
통합 대시보드: Traefik은 실시간으로 트래픽 및 서비스 상태를 모니터링하기 위한 사용자 친화적인 대시보드를 제공합니다.
이벤트 기반 아키텍처에서 Kafka와 Traefik을 사용하여 실시간 데이터 처리를 효율적으로 처리하면서 높은 가용성과 확장성을 유지할 수 있는 반응성 시스템을 구축할 수 있습니다.
환경 설정 방법
Ubuntu 24.04에 Docker 설치 방법
- 필수 패키지 설치.
sudo apt install ca-certificates curl gnupg lsb-release
- Docker의 공식 GPG 키 추가.
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
- APT 소스에 Docker 저장소 추가.
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
- 패키지 인덱스를 다시 업데이트하고 Docker Engine과 Docker Compose 플러그인을 함께 설치합니다.
sudo apt update
sudo apt install docker-ce docker-ce-cli containerd.io docker-compose-plugin
- 설치를 확인하기 위해 확인합니다.
sudo docker run hello-world
예상 출력:
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
c1ec31eb5944: Pull complete
Digest: sha256:305243c734571da2d100c8c8b3c3167a098cab6049c9a5b066b6021a60fcb966
Status: Downloaded newer image for hello-world:latest
Hello from Docker!
This message shows that your installation appears to be working correctly.
Docker Compose 구성 방법
Docker Compose를 사용하면 멀티 컨테이너 애플리케이션을 쉽게 관리할 수 있으며, 서비스를 정의하고 실행할 수 있는 단일 파일을 만들 수 있습니다.
- 프로젝트 디렉토리 만들기
mkdir ~/kafka-traefik-setup && cd ~/kafka-traefik-setup
docker-compose.yml
파일 만들기.
nano docker-compose.yml
- 파일에 다음 구성을 추가하여 서비스를 정의합니다.
version: '3.8'
services:
kafka:
image: wurstmeister/kafka:latest
ports:
- "9092:9092"
environment:
KAFKA_ADVERTISED_LISTENERS: INSIDE://kafka:9092,OUTSIDE://localhost:9092
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: INSIDE:PLAINTEXT,OUTSIDE:PLAINTEXT
KAFKA_LISTENERS: INSIDE://0.0.0.0:9092,OUTSIDE://0.0.0.0:9092
KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
zookeeper:
image: wurstmeister/zookeeper:latest
ports:
- "2181:2181"
traefik:
image: traefik:v2.9
ports:
- "80:80" # HTTP 트래픽
- "8080:8080" # Traefik 대시보드 (보안 취약)
command:
- "--api.insecure=true"
- "--providers.docker=true"
volumes:
- "/var/run/docker.sock:/var/run/docker.sock"
변경 사항을 ctrl + o
로 저장한 후 ctrl + x
로 종료합니다.
- 서비스를 시작합니다.
docker compose up -d
예상 출력:
[+] Running 4/4
✔ Network kafka-traefik-setup_default Created 0.2s
✔ Container kafka-traefik-setup-zookeeper-1 Started 1.9s
✔ Container kafka-traefik-setup-traefik-1 Started 1.9s
✔ Container kafka-traefik-setup-kafka-1 Started 1.9s
이벤트 주도 시스템 구축 방법
이벤트 프로듀서 생성 방법
Kafka에서 이벤트를 생성하려면 Kafka 프로듀서를 구현해야 합니다. 아래는 Java를 사용한 예시입니다.
kafka-producer.java
파일을 생성합니다.
nano kafka-producer.java
- 다음과 같은 Kafka 프로듀서를 위한 설정을 추가합니다.
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.clients.producer.RecordMetadata;
import java.util.Properties;
public class SimpleProducer {
public static void main(String[] args) {
// 프로듀서 속성 설정
Properties props = new Properties();
props.put("bootstrap.servers", "localhost:9092");
props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
// 프로듀서 생성
KafkaProducer<String, String> producer = new KafkaProducer<>(props);
try {
// "my-topic" 토픽으로 메시지 전송
ProducerRecord<String, String> record = new ProducerRecord<>("my-topic", "key1", "Hello, Kafka!");
RecordMetadata metadata = producer.send(record).get(); // 동기식 전송
System.out.printf("Sent message with key %s to partition %d with offset %d%n",
record.key(), metadata.partition(), metadata.offset());
} catch (Exception e) {
e.printStackTrace();
} finally {
// 프로듀서 닫기
producer.close();
}
}
}
변경 사항을 ctrl + o
로 저장한 후 ctrl + x
로 종료합니다.
위 설정에서 프로듀서는 “key1” 키와 “Hello, Kafka!” 값을 “my-topic” 토픽으로 메시지를 전송합니다.
Kafka 토픽 설정 방법
메시지를 생성하거나 수신하기 전에 Kafka에서 토픽을 생성해야 합니다.
- Kafka 설치와 함께 제공되는
kafka-topics.sh
스크립트를 사용하여 토픽을 만듭니다.
kafka-topics.sh --bootstrap-server localhost:9092 --create --topic <TopicName> --partitions <NumberOfPartitions> --replication-factor <ReplicationFactor>
예를 들어, 3개의 파티션과 복제 팩터가 1인 my-topic
이라는 토픽을 만들려면 다음을 실행합니다:
docker exec <Kafka Container ID> /opt/kafka/bin/kafka-topics.sh --bootstrap-server localhost:9092 --create --topic my-topic --partitions 3 --replication-factor 1
예상 출력:
Created topic my-topic.
- 토픽이 성공적으로 생성되었는지 확인합니다.
docker exec -it kafka-traefik-setup-kafka-1 /opt/kafka/bin/kafka-topics.sh --bootstrap-server localhost:9092 --list
예상 출력:
my-topic
이벤트 소비자 생성 방법
프로듀서 및 토픽을 생성한 후 해당 토픽에서 메시지를 읽을 수 있는 소비자를 생성할 수 있습니다.
kafka-consumer.java
파일을 만듭니다.
nano kafka-consumer.java
- 다음과 같이 Kafka 소비자를 위한 구성을 추가합니다.
import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import java.time.Duration;
import java.util.Collections;
import java.util.Properties;
public class SimpleConsumer {
public static void main(String[] args) {
// 소비자 속성 설정
Properties props = new Properties();
props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
props.put(ConsumerConfig.GROUP_ID_CONFIG, "my-group");
props.put(ConsumerConfig.KEY_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer");
props.put(ConsumerConfig.VALUE_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer");
// 소비자 생성
KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);
// 토픽 구독
consumer.subscribe(Collections.singletonList("my-topic"));
try {
while (true) {
// 새 레코드를 위해 폴링
ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));
for (ConsumerRecord<String, String> record : records) {
System.out.printf("Consumed message with key %s and value %s from partition %d at offset %d%n",
record.key(), record.value(), record.partition(), record.offset());
}
}
} finally {
// 소비자 종료
consumer.close();
}
}
}
변경 사항을 저장하려면 ctrl + o
를 입력하고, 종료하려면 ctrl + x
를 입력합니다.
위 구성에서 소비자는 my-topic
을 구독하고 계속해서 새 메시지를 폴링합니다. 메시지를 수신하면 키와 값과 함께 파티션 및 오프셋 정보를 출력합니다.
Traefik를 Kafka와 통합하는 방법
Traefik을 리버스 프록시로 구성하기.
Kafka를 위한 리버스 프록시로 Traefik을 통합하면 동적 라우팅 및 SSL 종료와 같은 기능을 제공하면서 수신 트래픽을 효율적으로 관리할 수 있습니다.
-
docker-compose.yml
파일을 업데이트하세요.
version: '3.8'
services:
kafka:
image: wurstmeister/kafka:latest
ports:
- "9092:9092"
environment:
KAFKA_ADVERTISED_LISTENERS: INSIDE://kafka:9092,OUTSIDE://localhost:9092
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: INSIDE:PLAINTEXT,OUTSIDE:PLAINTEXT
KAFKA_LISTENERS: INSIDE://0.0.0.0:9092,OUTSIDE://0.0.0.0:9092
KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
labels:
- "traefik.enable=true"
- "traefik.http.routers.kafka.rule=Host(`kafka.example.com`)"
- "traefik.http.services.kafka.loadbalancer.server.port=9092"
zookeeper:
image: wurstmeister/zookeeper:latest
ports:
- "2181:2181"
traefik:
image: traefik:v2.9
ports:
- "80:80" # HTTP 트래픽
- "8080:8080" # Traefik 대시보드 (보안되지 않음)
command:
- "--api.insecure=true"
- "--providers.docker=true"
volumes:
- "/var/run/docker.sock:/var/run/docker.sock"
이 구성에서 kafka.example.com
을 실제 도메인 이름으로 바꾸세요. 레이블은 Traefik이 Kafka 서비스로 트래픽을 유도하는 데 사용할 라우팅 규칙을 정의합니다.
- 서비스를 재시작하세요.
docker compose up -d
-
웹 브라우저에서
http://localhost:8080
에 접속하여 Traefik 대시보드에 접근하세요.Traefik을 통한 로드 밸런싱
Traefik은 Kafka 프로듀서와 소비자의 여러 인스턴스에 요청을 분산하는 데 도움이 되는 내장 로드 밸런싱 기능을 제공합니다.
이벤트 기반 마이크로서비스의 로드 밸런싱 전략
- 라운드 로빈:
기본적으로 Traefik은 들어오는 요청을 서비스의 모든 사용 가능한 인스턴스에 고르게 분산하기 위해 라운드 로빈 전략을 사용합니다. 이는 여러 Kafka 프로듀서 또는 소비자의 인스턴스가 실행될 때 로드를 균형 있게 분산하는 데 효과적입니다.
- 스티키 세션:
특정 클라이언트의 요청이 항상 동일한 인스턴스로 가도록 해야 하는 경우(예: 세션 상태 유지), 쿠키나 헤더를 사용하여 Traefik에서 스티키 세션을 구성할 수 있습니다.
- 헬스 체크:
Traefik에서 헬스 체크를 구성하여 트래픽이 Kafka 서비스의 건강한 인스턴스로만 라우팅되도록 합니다. 이는 docker-compose.yml
파일 내의 서비스 정의에 헬스 체크 매개변수를 추가하여 수행할 수 있습니다:
labels:
- "traefik.http.services.kafka.loadbalancer.healthcheck.path=/health"
- "traefik.http.services.kafka.loadbalancer.healthcheck.interval=10s"
- "traefik.http.services.kafka.loadbalancer.healthcheck.timeout=3s"
설정 테스트
이벤트 생산 및 소비 검증
- Kafka는 테스트를 위한 내장 명령줄 도구를 제공합니다. 콘솔 프로듀서를 시작하세요.
docker exec -it kafka-traefik-setup-kafka-1 /opt/kafka/bin/kafka-console-producer.sh --broker-list localhost:9092 --topic my-topic
이 명령을 실행한 후, 터미널에 메시지를 입력하면 지정된 Kafka 주제로 전송됩니다.
- 다른 터미널 세션을 시작하고 콘솔 소비자를 시작하십시오.
docker exec -it kafka-traefik-setup-kafka-1 /opt/kafka/bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic my-topic --from-beginning
이 명령은 소비자가 시작되기 전에 생성된 메시지를 포함하여 my-topic
의 모든 메시지를 표시합니다.
- 소비자가 생산자와 얼마나 잘 동기화되고 있는지 확인하려면 특정 소비자 그룹의 지연을 확인하기 위해 다음 명령을 실행할 수 있습니다.
docker exec -it kafka-traefik-setup-kafka-1 /opt/kafka/bin/kafka-consumer-groups.sh --bootstrap-server localhost:9092 --describe --group <your-consumer-group>
모니터링 및 로깅
- 카프카 메트릭:
카프카는 JMX(자바 관리 확장)를 사용하여 모니터링할 수 있는 수많은 메트릭을 노출합니다. JMX를 구성하여 이 메트릭을 Prometheus 또는 Grafana와 같은 모니터링 시스템으로 내보낼 수 있습니다. 모니터링할 주요 메트릭은 다음과 같습니다:
- 메시지 처리량: 생산 및 소비된 메시지의 비율입니다.
- 소비자 지연: 마지막으로 생산된 메시지 오프셋과 마지막으로 소비된 메시지 오프셋의 차이입니다.
- 브로커 상태: 요청 비율 및 오류 비율과 같은 브로커 성능과 관련된 메트릭입니다.
- Prometheus 및 Grafana 통합:
Kafka 메트릭을 시각화하려면 Prometheus를 설정하여 Kafka 브로커에서 메트릭을 스크랩할 수 있습니다. 다음 단계를 따르십시오:
-
브로커 구성에서 JMX Exporter를 Java 에이전트로 추가하여 Kafka 브로커에서 JMX Exporter를 활성화합니다.
-
프로메테우스를 구성하여 구성 파일(
prometheus.yml
)에서 JMX Exporter 엔드포인트를 가리키는 스크레이프 작업을 추가합니다. -
그라파나를 사용하여 실시간으로 이러한 지표를 시각화하는 대시보드를 생성합니다.
Traefik 모니터링 구현 방법
- Traefik 메트릭 엔드포인트.
Traefik은 Prometheus를 통해 메트릭을 내보내는 기능을 내장하고 있습니다. 이 기능을 활성화하려면 docker-compose.yml
내 Traefik 서비스 정의에 다음 구성을 추가하십시오:
command:
- "--metrics.prometheus=true"
- "--metrics.prometheus.addservice=true"
- 그라파나를 사용하여 Traefik 메트릭 시각화.
한번 Prometheus가 Traefik 메트릭을 스크래핑하면, 그라파나를 사용하여 시각화할 수 있습니다:
-
그라파나에서 새 대시보드를 생성하고 다음과 같은 주요 Traefik 메트릭을 표시하는 패널을 추가하십시오:
-
traefik_entrypoint_requests_total: 받은 총 요청 수.
-
traefik_backend_request_duration_seconds: 백엔드 서비스의 응답 시간.
-
traefik_service_requests_total: 백엔드 서비스로 전달된 총 요청 수.
- 알림 설정.
Prometheus 또는 Grafana에서 특정 임계값(예: 고객 지연 또는 증가된 오류율)에 따라 경고를 구성합니다.
결론
이 가이드에서는 Ubuntu 24.04 환경 내에서 Kafka와 Traefik을 사용하여 이벤트 주도 아키텍처(EDA)를 성공적으로 구현했습니다.
추가 자료
더 알아보려면 방문하세요:
-
The Traefik 공식 문서
-
The Docker 공식 문서
-
Ubuntu 24.04에 Traefik Proxy 설정하는 방법에 대한 Vultr 안내
Source:
https://www.freecodecamp.org/news/how-to-implement-event-driven-data-processing/