Đơn vị biên soạn: TechData.AI
Chủ đề: Data Engineering và Event Streaming
Đối tượng: Người mới học Kafka, Data Engineer và Backend Developer
Phiên bản tham chiếu: Apache Kafka 4.x
MỤC LỤC
- Mục tiêu bài học
- Môi trường thực hành
- KRaft thay đổi cách cài Kafka như thế nào
- Các chế độ triển khai KRaft
- Xây dựng Kafka bằng Docker Compose
- Khởi động và kiểm tra Cluster
- Tạo Topic đầu tiên
- Gửi và đọc Event bằng Kafka CLI
- Kiểm tra Partition và Offset
- Bổ sung Kafka UI
- Hiểu listeners và advertised.listeners
- Lưu dữ liệu bền vững bằng Volume
- Các lỗi thường gặp
- Khi nào cần Cluster nhiều Broker
- Bài thực hành cuối bài
- Tổng kết
- Câu hỏi ôn tập
- Tài liệu tham khảo
1. Mục tiêu bài học
Trong bài trước, chúng ta đã tìm hiểu Apache Kafka là gì, vì sao Kafka phù hợp với kiến trúc Event Streaming và vai trò của Producer, Consumer, Broker, Topic, Partition cùng Offset. Bài này chuyển từ lý thuyết sang thực hành bằng cách xây dựng một môi trường Kafka hoàn chỉnh trên máy cá nhân.
Sau khi hoàn thành, học viên có thể tự cài Kafka bằng Docker, khởi động Cluster sử dụng KRaft, tạo Topic, gửi Event, đọc Event và quan sát dữ liệu bằng giao diện web. Quan trọng hơn, học viên sẽ hiểu ý nghĩa của những cấu hình thường gây lỗi như listeners, advertised.listeners, process.roles và controller.quorum.voters.
Môi trường trong bài được thiết kế cho học tập và phát triển phần mềm. Nó chưa phải kiến trúc Production. Ở cuối bài, chúng ta sẽ phân biệt rõ những thành phần nào có thể giữ lại và những thành phần nào cần thay đổi khi triển khai trong doanh nghiệp.
2. Môi trường thực hành

Học viên cần chuẩn bị:
- Máy tính có tối thiểu 8 GB RAM.
- Docker Desktop hoặc Docker Engine.
- Docker Compose phiên bản mới.
- Terminal trên macOS, Linux hoặc Windows PowerShell.
- Trình soạn thảo như Visual Studio Code.
- Khoảng 5 GB dung lượng trống.
Kiểm tra Docker:
docker --version
docker compose versionNếu hai lệnh trả về phiên bản hợp lệ, môi trường đã sẵn sàng. Kafka 4.x yêu cầu Java 17 trở lên khi chạy trực tiếp từ bộ cài, nhưng trong bài này Java đã nằm trong Docker Image nên máy cá nhân không cần cài Java riêng.
Tạo cấu trúc thư mục:
kafka-lab/
docker-compose.yml
data/
scripts/
producer/
consumer/
Thư mục data sẽ được dùng để lưu dữ liệu Kafka. Các thư mục producer và consumer được sử dụng trong những bài tiếp theo.
3. KRaft thay đổi cách cài Kafka như thế nào

Các hướng dẫn Kafka cũ thường yêu cầu khởi động ZooKeeper trước, sau đó mới khởi động Kafka Broker. Cách triển khai này không còn phù hợp với Kafka 4.
KRaft là cơ chế quản lý metadata được tích hợp trong Kafka. Metadata của Cluster gồm danh sách Broker, Topic, Partition, Replica, cấu hình và trạng thái bầu chọn Leader. Thay vì lưu metadata trong ZooKeeper, KRaft sử dụng một log đồng thuận do nhóm Controller quản lý.
Một Kafka Node có thể mang vai trò broker, controller hoặc đồng thời cả hai. Môi trường học tập thường dùng Combined Mode vì chỉ cần một container. Production nên cân nhắc tách Controller khỏi Broker để cô lập tài nguyên và giảm ảnh hưởng khi Broker xử lý tải lớn.
KRaft sử dụng Cluster ID để nhận diện Cluster. Log directory phải được format và gắn với Cluster ID trước khi Broker hoạt động. Docker Image chính thức có thể tự động hóa bước này thông qua biến môi trường, nhưng Data Engineer vẫn cần hiểu bản chất để xử lý sự cố.
4. Các chế độ triển khai KRaft

4.1 Combined Mode
Một Node vừa làm Broker vừa làm Controller:
process.roles=broker,controller
Ưu điểm là cấu hình đơn giản, ít tài nguyên và phù hợp với máy cá nhân. Nhược điểm là dữ liệu, request của Client và công việc quản lý metadata cùng chia sẻ một tiến trình.
4.2 Separated Mode
Controller và Broker chạy trên các Node riêng. Controller Quorum thường có ba hoặc năm Node để chịu lỗi. Broker chỉ tập trung lưu trữ và phục vụ Event.
Đây là lựa chọn phù hợp hơn cho Production, đặc biệt khi Cluster có nhiều Topic, Partition hoặc tải lớn. Tuy nhiên, môi trường thực hành không cần độ phức tạp này ngay từ đầu.
5. Xây dựng Kafka bằng Docker Compose

Tạo file docker-compose.yml với nội dung sau:
services:
kafka:
image: apache/kafka:4.3.1
container_name: kafka
hostname: kafka
ports:
- "9092:9092"
environment:
KAFKA_NODE_ID: 1
KAFKA_PROCESS_ROLES: broker,controller
KAFKA_CONTROLLER_QUORUM_VOTERS: 1@kafka:9093
KAFKA_LISTENERS: CONTROLLER://:9093,PLAINTEXT://:29092,EXTERNAL://:9092
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:29092,EXTERNAL://localhost:9092
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT,EXTERNAL:PLAINTEXT
KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER
KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1
KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1
KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS: 0
KAFKA_NUM_PARTITIONS: 3
volumes:
- kafka_data:/var/lib/kafka/data
volumes:
kafka_data:Cấu hình này công bố hai địa chỉ kết nối. Các ứng dụng chạy trong cùng Docker Network sử dụng kafka:29092. Ứng dụng chạy trên máy cá nhân sử dụng localhost:9092.
KAFKA_NODE_ID là định danh duy nhất của Node. KAFKA_PROCESS_ROLES xác định Node vừa làm Broker vừa làm Controller. KAFKA_CONTROLLER_QUORUM_VOTERS cho biết Controller có Node ID 1 và lắng nghe tại cổng 9093.
Các cấu hình Replication Factor được đặt bằng 1 vì môi trường chỉ có một Broker. Tuyệt đối không sao chép nguyên giá trị này sang Production nếu dữ liệu cần khả năng chịu lỗi.
6. Khởi động và kiểm tra Cluster

Khởi động dịch vụ:
docker compose up -dKiểm tra container:
docker compose psTheo dõi log:
docker compose logs -f kafkaKafka cần một khoảng thời gian ngắn để format storage, khởi động Controller và đăng ký Broker. Khi log cho thấy Broker đã chuyển sang trạng thái hoạt động, Cluster sẵn sàng nhận kết nối.
Kiểm tra API version:
docker exec kafka /opt/kafka/bin/kafka-broker-api-versions.sh \
--bootstrap-server localhost:9092bootstrap-server không có nghĩa Client chỉ giao tiếp với một Broker. Đây là địa chỉ ban đầu để Client lấy metadata. Sau đó, Client sẽ kết nối trực tiếp với Broker phù hợp cho từng Partition.
7. Tạo Topic đầu tiên

Tạo Topic order-created với ba Partition:
docker exec kafka /opt/kafka/bin/kafka-topics.sh \
--bootstrap-server localhost:9092 \
--create \
--topic order-created \
--partitions 3 \
--replication-factor 1Liệt kê Topic:
docker exec kafka /opt/kafka/bin/kafka-topics.sh \
--bootstrap-server localhost:9092 \
--listXem chi tiết Topic:
docker exec kafka /opt/kafka/bin/kafka-topics.sh \
--bootstrap-server localhost:9092 \
--describe \
--topic order-createdKết quả cho biết Topic có ba Partition. Vì Cluster chỉ có một Broker, Leader và Replica đều nằm trên Node 1. Trong Cluster nhiều Broker, các Partition và Replica được phân phối trên nhiều Node.
Không nên phụ thuộc hoàn toàn vào cơ chế tự động tạo Topic. Production nên tạo Topic bằng quy trình kiểm soát, Infrastructure as Code hoặc công cụ quản trị để bảo đảm đúng Partition, Replication Factor và retention.
8. Gửi và đọc Event bằng Kafka CLI

Mở Producer Console:
docker exec -it kafka /opt/kafka/bin/kafka-console-producer.sh \
--bootstrap-server localhost:9092 \
--topic order-createdNhập từng Event JSON:
{"order_id":"ORD-10001","customer_id":"CUS-001","total_amount":1250000,"status":"created"}
{"order_id":"ORD-10002","customer_id":"CUS-002","total_amount":890000,"status":"created"}
{"order_id":"ORD-10003","customer_id":"CUS-001","total_amount":2150000,"status":"created"}Mỗi dòng trở thành một record. Dừng Producer bằng Ctrl+C.
Mở Consumer Console:
docker exec -it kafka /opt/kafka/bin/kafka-console-consumer.sh \
--bootstrap-server localhost:9092 \
--topic order-created \
--from-beginningTùy chọn --from-beginning yêu cầu Consumer đọc từ Offset sớm nhất còn được giữ lại. Nếu bỏ tùy chọn này, Console Consumer thường chỉ hiển thị Event xuất hiện sau khi Consumer bắt đầu.
Kafka không xóa Event chỉ vì Console Consumer đã đọc. Chạy lại lệnh với --from-beginning, chúng ta vẫn có thể xem những Event cũ.
9. Kiểm tra Partition và Offset

Để quan sát metadata của từng record, chạy:
docker exec -it kafka /opt/kafka/bin/kafka-console-consumer.sh \
--bootstrap-server localhost:9092 \
--topic order-created \
--from-beginning \
--property print.partition=true \
--property print.offset=true \
--property print.timestamp=trueVì các Event trước chưa có Key, chúng có thể được phân phối theo cơ chế mặc định của Producer Console. Để gửi Key và Value, sử dụng:
docker exec -it kafka /opt/kafka/bin/kafka-console-producer.sh \
--bootstrap-server localhost:9092 \
--topic order-created \
--property parse.key=true \
--property key.separator=:Nhập dữ liệu:
ORD-10001:{"order_id":"ORD-10001","status":"paid"}
ORD-10001:{"order_id":"ORD-10001","status":"packed"}
ORD-10002:{"order_id":"ORD-10002","status":"paid"}
Hai Event của ORD-10001 có cùng Key nên được đưa vào cùng một Partition. Đây là nền tảng để giữ thứ tự sự kiện của một đơn hàng.
10. Bổ sung Kafka UI

CLI phù hợp để kiểm tra nhanh nhưng không thuận tiện khi cần quan sát nhiều Topic và Consumer Group. Có thể bổ sung Kafka UI vào Docker Compose:
kafka-ui:
image: provectuslabs/kafka-ui:latest
container_name: kafka-ui
depends_on:
- kafka
ports:
- "8080:8080"
environment:
KAFKA_CLUSTERS_0_NAME: local-kafka
KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS: kafka:29092Khởi động lại:
docker compose up -dMở http://localhost:8080. Giao diện cho phép xem Broker, Topic, Partition, Message và Consumer Group.
Kafka UI là công cụ bên ngoài Apache Kafka. Production cần khóa truy cập, bật xác thực và không công khai giao diện quản trị ra Internet.
11. Hiểu listeners và advertised.listeners

Đây là phần gây nhiều lỗi nhất khi cài Kafka bằng Docker.
listeners xác định địa chỉ và cổng mà Kafka thực sự mở để nhận kết nối. advertised.listeners xác định địa chỉ Kafka công bố cho Client sau bước bootstrap.
Client có thể kết nối thành công đến localhost:9092 ở bước đầu nhưng sau đó vẫn lỗi nếu Broker trả về địa chỉ kafka:29092 mà máy cá nhân không phân giải được. Ngược lại, container khác không thể dùng localhost:9092 vì localhost bên trong container chỉ chính container đó.
Vì vậy, môi trường Docker thường cần hai listener:
- Listener nội bộ cho container trong Docker Network.
- Listener bên ngoài cho ứng dụng chạy trên máy cá nhân.
Khi triển khai trên máy chủ hoặc Kubernetes, địa chỉ advertised phải là DNS hoặc IP mà Client thực sự truy cập được. Không nên sửa ngẫu nhiên cổng cho đến khi chạy được. Cần xác định rõ Client đang ở mạng nào và Broker phải công bố địa chỉ nào.
12. Lưu dữ liệu bền vững bằng Volume

Kafka lưu Event trên ổ đĩa. Nếu container bị xóa mà không có Volume, dữ liệu có thể mất theo container.
Named Volume trong Docker Compose giúp dữ liệu tồn tại qua những lần khởi động lại:
volumes:
- kafka_data:/var/lib/kafka/dataDừng container nhưng giữ dữ liệu:
docker compose downKhởi động lại:
docker compose up -dTopic và Event vẫn còn trong Volume.
Lệnh sau xóa cả container và Volume:
docker compose down -vChỉ dùng khi chắc chắn muốn xóa toàn bộ dữ liệu thực hành. Trong Production, Volume không thay thế Replication, backup hoặc Disaster Recovery.
13. Các lỗi thường gặp

Kafka khởi động rồi dừng
Kiểm tra log bằng docker compose logs kafka. Nguyên nhân thường là biến môi trường sai, cổng bị chiếm, quyền ghi Volume hoặc metadata cũ không tương thích với Cluster ID.
Client kết nối bootstrap thành công nhưng không gửi được dữ liệu
Kiểm tra advertised.listeners. Broker có thể đang trả về hostname mà Client không truy cập được.
Topic yêu cầu Replication Factor lớn hơn số Broker
Một Cluster một Broker không thể tạo Topic có Replication Factor bằng 3. Môi trường học tập dùng giá trị 1. Production nên có ít nhất ba Broker nếu Topic quan trọng cần ba Replica.
Kafka UI không thấy Cluster
Kafka UI chạy trong Docker Network nên phải dùng kafka:29092, không dùng localhost:9092.
Port 9092 đã được sử dụng
Kiểm tra ứng dụng khác đang giữ cổng hoặc đổi cổng bên ngoài, chẳng hạn 19092:9092, đồng thời cập nhật EXTERNAL advertised listener.
Consumer không thấy Event cũ
Thêm --from-beginning hoặc sử dụng một Consumer Group mới. Consumer Group đã commit Offset sẽ tiếp tục từ vị trí đã lưu.
14. Khi nào cần Cluster nhiều Broker

Cluster một Broker phù hợp để học, phát triển và chạy kiểm thử cục bộ. Nó không có khả năng chịu lỗi vì tất cả Partition chỉ tồn tại trên một Node.
Doanh nghiệp nên cân nhắc nhiều Broker khi:
- Dữ liệu không được phép mất khi một máy chủ hỏng.
- Throughput vượt khả năng của một Broker.
- Cần bảo trì mà không dừng toàn hệ thống.
- Cần phân phối Partition và Replica giữa nhiều máy.
- Có yêu cầu SLA hoặc Disaster Recovery.
Cluster ba Broker là điểm khởi đầu phổ biến, không phải quy tắc tuyệt đối. Kích thước Cluster phải dựa trên throughput, retention, Replication Factor, tốc độ ổ đĩa, băng thông mạng và mục tiêu phục hồi.
15. Bài thực hành cuối bài

Hãy hoàn thành các yêu cầu sau:
- Khởi động Kafka và Kafka UI bằng Docker Compose.
- Tạo Topic
payment-eventscó ba Partition. - Gửi năm Event thanh toán dạng JSON.
- Dùng
payment_idlàm Key. - Đọc Event và hiển thị Key, Partition, Offset, Timestamp.
- Dừng toàn bộ container nhưng giữ Volume.
- Khởi động lại và chứng minh dữ liệu vẫn tồn tại.
- Mở Kafka UI và xác nhận số Partition của Topic.
Học viên nên ghi lại kết quả và giải thích vì sao các Event cùng Key nằm trong cùng một Partition.
16. Tổng kết
Trong bài này, chúng ta đã xây dựng một môi trường Apache Kafka 4 sử dụng Docker và KRaft. Kafka Cluster có một Node đảm nhiệm đồng thời vai trò Broker và Controller, phù hợp cho học tập nhưng chưa phù hợp với Production.
Các điểm cần ghi nhớ:
- Kafka 4 sử dụng KRaft và không cần ZooKeeper.
listenerslà địa chỉ Kafka mở để lắng nghe.advertised.listenerslà địa chỉ Kafka công bố cho Client.- Client trong Docker và Client trên máy cá nhân cần địa chỉ khác nhau.
- Topic được chia thành Partition.
- Record trong mỗi Partition có Offset riêng.
- Docker Volume giúp giữ dữ liệu khi container khởi động lại.
- Cluster một Broker không có khả năng chịu lỗi.
Bài tiếp theo sẽ sử dụng Python để xây dựng Kafka Producer, gửi Event JSON, lựa chọn Message Key và tối ưu độ tin cậy khi truyền dữ liệu.
17. Câu hỏi ôn tập
- KRaft thay thế thành phần nào trong kiến trúc Kafka cũ?
- Broker và Controller khác nhau như thế nào?
- Vì sao môi trường Docker cần listener nội bộ và bên ngoài?
bootstrap-servercó phải Broker duy nhất Client sử dụng không?- Vì sao Cluster một Broker chỉ dùng Replication Factor bằng 1?
- Docker Volume giải quyết vấn đề gì?
--from-beginningảnh hưởng đến Console Consumer như thế nào?- Vì sao các Event cùng Key thường nằm trong cùng Partition?
18. Tài liệu tham khảo
- Apache Kafka Quickstart: https://kafka.apache.org/quickstart/
- Apache Kafka Documentation: https://kafka.apache.org/documentation/
- Apache Kafka Docker Image Usage Guide: https://github.com/apache/kafka/blob/trunk/docker/examples/README.md
- Apache Kafka KRaft Configuration: https://kafka.apache.org/documentation/#kraft
- Apache Kafka Topic Operations: https://kafka.apache.org/documentation/#basic_ops_add_topic
