ติดตั้ง Apache Kafka บน VPS ระบบ Event Streaming
Apache Kafka คือ distributed message broker สำหรับ event streaming — ออกแบบให้ส่ง message ได้นับล้านต่อวินาที ด้วยประสิทธิภาพสูง ทำให้เหมาะสำหรับระบบ real-time data pipeline ต่างจาก RabbitMQ ที่เหมาะสำหรับการส่งข้อความแบบเป้าหมายชัดเจน Kafka เหมาะสำหรับการเก็บเหตุการณ์ (event log) ที่ต้องการให้ consumer หลายตัวอ่านได้พร้อมกัน บทความนี้สอนตั้ง Kafka cluster ด้วย Docker Compose พร้อม Kafka UI dashboard, producer/consumer example, และการจัดการ topic
สถาปัตยกรรม Apache Kafka
- Broker: ตัวจัดเก็บ message — จำเป็นต้องมี 3 ตัวขึ้นไปสำหรับ production (ที่นี่ใช้ 1-2 ตัว)
- Topic: ชื่อช่องที่ producer ส่ง message ไปและ consumer อ่านออกมา (เหมือน queue)
- Partition: topic แบ่งเป็น partition ต่างๆ เพื่อให้ consumer เหลื่อมกันประมวลผล (parallelize)
- Consumer Group: กลุ่ม consumer ที่บ่งชี้ว่าเป็นคนเดียวกัน ใช้ retry/offset management
- Replication Factor: จำนวนสำเนาของแต่ละ partition ถ้า broker ล้ม partition อื่นจะมีสำเนา
- Zookeeper/KRaft: Kafka ใหม่ใช้ KRaft (ข้อมูล metadata เก็บใน broker เอง ไม่ต้อง Zookeeper แยก)
ติดตั้ง Kafka ด้วย Docker Compose
mkdir -p /opt/kafka && cd /opt/kafka
# สร้าง docker-compose.yml
cat > docker-compose.yml << 'EOF'
version: "3.8"
services:
kafka:
image: confluentinc/cp-kafka:7.5.0
restart: unless-stopped
container_name: kafka-broker
ports:
- "127.0.0.1:9092:9092"
- "127.0.0.1:29092:29092"
environment:
KAFKA_NODE_ID: 1
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:29092,PLAINTEXT_HOST://localhost:9092
KAFKA_PROCESS_ROLES: broker,controller
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
KAFKA_CONTROLLER_QUORUM_VOTERS: 1@kafka:29093
KAFKA_LISTENERS: PLAINTEXT://kafka:29092,CONTROLLER://kafka:29093,PLAINTEXT_HOST://0.0.0.0:9092
KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT
KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER
KAFKA_LOG_DIRS: /var/lib/kafka/data
KAFKA_AUTO_CREATE_TOPICS_ENABLE: "true"
CLUSTER_ID: MkwNYEubTXePEKdc6tH9kQ
kafka-ui:
image: provectuslabs/kafka-ui:latest
restart: unless-stopped
container_name: kafka-ui
ports:
- "127.0.0.1:8080:8080"
environment:
KAFKA_CLUSTERS_0_NAME: kafka-cluster
KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS: kafka:29092
volumes:
kafka_data:
EOF
docker-compose up -d
ตั้ง Nginx Reverse Proxy สำหรับ Kafka UI
nano /etc/nginx/sites-available/kafka-ui
server {
listen 80;
server_name kafka.yourdomain.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name kafka.yourdomain.com;
ssl_certificate /etc/letsencrypt/live/kafka.yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/kafka.yourdomain.com/privkey.pem;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_buffering off;
}
}
ln -s /etc/nginx/sites-available/kafka-ui /etc/nginx/sites-enabled/
nginx -t && systemctl reload nginx
certbot --nginx -d kafka.yourdomain.com
สร้าง Topic และ Producer/Consumer
# ตรวจสอบว่า Kafka ready
docker exec kafka-broker kafka-broker-api-versions.sh --bootstrap-server localhost:9092
# สร้าง topic "orders" ด้วย 3 partition
docker exec kafka-broker kafka-topics.sh --bootstrap-server localhost:9092 \
--create --topic orders \
--partitions 3 \
--replication-factor 1
# ดู topic ที่สร้าง
docker exec kafka-broker kafka-topics.sh --bootstrap-server localhost:9092 --list
# ดูรายละเอียด topic
docker exec kafka-broker kafka-topics.sh --bootstrap-server localhost:9092 \
--describe --topic orders
Producer — ส่ง Message ไปยัง Topic
# เปิด console producer สำหรับ topic "orders"
docker exec -it kafka-broker kafka-console-producer.sh \
--bootstrap-server localhost:9092 \
--topic orders
# พิมพ์ message แล้วกด Enter (ทำให้ producer ส่งไปยัง topic)
# ตัวอย่าง message:
{"order_id": 1001, "customer": "Alice", "amount": 500}
{"order_id": 1002, "customer": "Bob", "amount": 1200}
{"order_id": 1003, "customer": "Charlie", "amount": 750}
Consumer — อ่าน Message จาก Topic
# เปิด console consumer สำหรับ topic "orders" (อ่าน message ทั้งหมดตั้งแต่เริ่มต้น)
docker exec -it kafka-broker kafka-console-consumer.sh \
--bootstrap-server localhost:9092 \
--topic orders \
--from-beginning \
--group my-consumer-group
# ถ้าต้องการอ่าน message เฉพาะอันที่ส่งใหม่ (ตั้งแต่ตอนนี้เป็นต้นไป)
docker exec -it kafka-broker kafka-console-consumer.sh \
--bootstrap-server localhost:9092 \
--topic orders \
--group my-consumer-group
# ดู consumer group ทั้งหมด
docker exec kafka-broker kafka-consumer-groups.sh \
--bootstrap-server localhost:9092 \
--list
# ดู offset ของ consumer group
docker exec kafka-broker kafka-consumer-groups.sh \
--bootstrap-server localhost:9092 \
--describe \
--group my-consumer-group
ใช้ Kafka Producer/Consumer ใน Application (Python)
# ติดตั้ง library
pip install kafka-python
# Producer (send_order.py)
from kafka import KafkaProducer
import json
producer = KafkaProducer(
bootstrap_servers=['localhost:9092'],
value_serializer=lambda v: json.dumps(v).encode('utf-8')
)
# ส่ง message 10 ครั้ง
for i in range(10):
message = {
'order_id': 1000 + i,
'customer': f'Customer{i}',
'amount': 100 * (i + 1)
}
producer.send('orders', value=message)
print(f"Sent: {message}")
producer.close()
# Consumer (consume_order.py)
from kafka import KafkaConsumer
import json
consumer = KafkaConsumer(
'orders',
bootstrap_servers=['localhost:9092'],
group_id='python-consumer',
auto_offset_reset='earliest',
value_deserializer=lambda m: json.loads(m.decode('utf-8'))
)
for message in consumer:
print(f"Received: {message.value}")
print(f"Partition: {message.partition}, Offset: {message.offset}")
ติดตั้ง Kafka Connect สำหรับ Data Pipeline
# เพิ่มใน docker-compose.yml (เหนือ volumes:)
kafka-connect:
image: confluentinc/cp-kafka-connect:7.5.0
restart: unless-stopped
container_name: kafka-connect
ports:
- "127.0.0.1:8083:8083"
environment:
CONNECT_BOOTSTRAP_SERVERS: kafka:29092
CONNECT_GROUP_ID: connect-cluster
CONNECT_CONFIG_STORAGE_TOPIC: _connect-configs
CONNECT_OFFSET_STORAGE_TOPIC: _connect-offsets
CONNECT_STATUS_STORAGE_TOPIC: _connect-status
CONNECT_CONFIG_STORAGE_REPLICATION_FACTOR: 1
CONNECT_OFFSET_STORAGE_REPLICATION_FACTOR: 1
CONNECT_STATUS_STORAGE_REPLICATION_FACTOR: 1
CONNECT_REST_ADVERTISED_HOST_NAME: kafka-connect
CONNECT_REST_PORT: 8083
depends_on:
- kafka
# Restart docker-compose
docker-compose down && docker-compose up -d
# ตรวจสอบว่า Kafka Connect พร้อม
curl -s http://localhost:8083 | python3 -m json.tool
# ดู plugins ที่ติดตั้งแล้ว
curl -s http://localhost:8083/connector-plugins | python3 -m json.tool
retention.ms เพื่อลบ message เก่าโดยอัตโนมัติ ประหยัด disk spaceตรวจสอบว่า Kafka ทำงานถูกต้อง
# ตรวจ container ทั้งหมด
docker-compose ps
# ตรวจสอบ Kafka broker status ด้วย API
curl -s http://localhost:8080/api/clusters/kafka-cluster | python3 -m json.tool
# ดู topics ทั้งหมด
docker exec kafka-broker kafka-topics.sh --bootstrap-server localhost:9092 --list
# ดูขนาด log ของ topic
docker exec kafka-broker kafka-log-dirs.sh --bootstrap-server localhost:9092 --describe --topic-list orders
# ตรวจ consumer group offset
docker exec kafka-broker kafka-consumer-groups.sh \
--bootstrap-server localhost:9092 \
--describe --group my-consumer-group
# ดู metrics ของ broker (JMX)
curl -s http://localhost:9999 2>/dev/null || echo "JMX metrics ไม่เปิด"
แก้ปัญหาที่พบบ่อย
Error: "Connection refused" เมื่อ producer/consumer เชื่อมต่อ
Broker ยังไม่พร้อม หรือ advertised listener ไม่ถูกต้อง — ตรวจสอบว่า KAFKA_ADVERTISED_LISTENERS ชี้ที่ host/port ที่ client สามารถเชื่อมต่อได้
# ตรวจ logs
docker-compose logs kafka
# ถ้า broker ยังไม่เริ่ม รอสัก 10-15 วินาที แล้ว restart
docker-compose restart kafka
# ตรวจ port 9092 ว่าเปิดอยู่
netstat -tlnp | grep 9092 || ss -tlnp | grep 9092
Error: "Topic already exists" เมื่อสร้าง topic ที่มีชื่อเดียวกัน
Topic ได้ถูกสร้างไปแล้ว — ถ้าต้องการสร้างใหม่ให้ลบเก่าออกก่อน
# ลบ topic
docker exec kafka-broker kafka-topics.sh --bootstrap-server localhost:9092 \
--delete --topic orders
# รอสัก 2-3 วินาที แล้วสร้างใหม่
docker exec kafka-broker kafka-topics.sh --bootstrap-server localhost:9092 \
--create --topic orders --partitions 3 --replication-factor 1
Consumer ไม่อ่าน message แม้ producer ส่งไปแล้ว
Consumer group มี offset ชี้อยู่ไกล — ถ้ากำหนด --from-beginning ต้องตรวจสอบ auto.offset.reset เป็น earliest
# ดู offset ปัจจุบัน
docker exec kafka-broker kafka-consumer-groups.sh \
--bootstrap-server localhost:9092 \
--describe \
--group my-consumer-group
# รีเซ็ต offset ไปยัง earliest
docker exec kafka-broker kafka-consumer-groups.sh \
--bootstrap-server localhost:9092 \
--reset-offsets \
--to-earliest \
--execute \
--group my-consumer-group \
--topic orders
# ลอง consumer อีกครั้ง
docker exec -it kafka-broker kafka-console-consumer.sh \
--bootstrap-server localhost:9092 \
--topic orders \
--group my-consumer-group
Kafka UI ไม่แสดง messages
ปกติเกิดจากการเข้าสู่ระบบ UI ช้า หรือ bootstrap connection หลุด — ลอง refresh หน้า
# ตรวจ Kafka UI logs
docker-compose logs kafka-ui
# ตรวจ connection จาก UI ไปยัง broker
docker exec kafka-ui curl -s http://kafka:29092 -I
# ถ้าไม่ได้ restart UI
docker-compose restart kafka-ui
Disk space เต็มเพราะ Kafka log ใหญ่เกินไป
Topic มี retention policy ที่ไม่จำกัด — ตั้ง retention.ms เพื่อลบ message เก่า
# ดู segment ของ topic
ls -lh /var/lib/docker/volumes/kafka_data/_data/
# ตั้ง retention ให้ลบ message เก่ากว่า 24 ชั่วโมง
docker exec kafka-broker kafka-configs.sh --bootstrap-server localhost:9092 \
--entity-type topics \
--entity-name orders \
--alter \
--add-config retention.ms=86400000
# ลบ segments เก่า
docker exec kafka-broker kafka-log-cleaner.sh --bootstrap-server localhost:9092
คำถามที่พบบ่อย
Kafka กับ RabbitMQ ต่างกันอย่างไร?
Kafka ออกแบบมาสำหรับ event streaming ที่ต้องการให้หลาย consumer อ่านข้อมูลเดียวกันได้ (fan-out) และเก็บ message เป็นเวลานาน RabbitMQ เหมาะสำหรับ message queue ที่ต้องส่งไปยัง consumer เพียงตัวเดียว (point-to-point) และ message ลบออกเมื่อ consumer อ่านแล้ว Kafka ใช้เยอะขึ้นสำหรับระบบ modern data pipeline ที่ต้องการ data replay + multiple subscribers
Kafka partition ต้องตั้งกี่ตัว?
ขึ้นกับจำนวน consumer และปริมาณข้อมูล — หลักการคือ จำนวน partition ควรเท่ากับจำนวน consumer สูงสุด เพราะแต่ละ consumer ใน group จะอ่าน partition เพียงตัวเดียว ถ้า partition มีมากกว่า consumer จะมี partition ที่ไม่มี consumer อ่าน ถ้า consumer มากกว่า partition จะมี consumer ที่ว่างงาน เริ่มต้นตั้ง 3 partition ก่อน แล้วปรับตามการใช้งานจริง
เก็บ Kafka message ได้นานแค่ไหน?
ขึ้นกับ retention policy และ disk space — ตั้ง retention.ms เป็นเวลาที่ต้องการ (เช่น 7 วัน = 604800000 milliseconds) หรือ retention.bytes เป็นขนาดสูงสุด (เช่น 10 GB) Kafka จะลบ segment เก่าโดยอัตโนมัติ ส่วน message ใหม่ที่ยังไม่ลบ consumer ใหม่สามารถอ่านได้ (ต่างจาก RabbitMQ ที่ลบเมื่อ consumer อ่านแล้ว)