Chạy Kafka trong Docker để thử nghiệm chỉ cần một lệnh. Chạy nó thật thì cần vài dòng nữa, và bài này đo chuyện gì xảy ra khi thiếu từng dòng.

Mất dữ liệu vì thiếu volume, OOMKilled vì giới hạn bộ nhớ, và sáu dòng phải có

Thiếu volume: 10.000 tin biến mất

Chạy Kafka không khai volume, ghi 10.000 tin, rồi docker rm:

trước:   quan-trong:0:10000
sau:     Could not match any topic-partitions with the specified filters
danh sách topic sau khi dựng lại:   (rỗng)

Không chỉ mất dữ liệu — mất luôn topic. Broker lên lại sạch sẽ như vừa cài, và không một dòng log nào nói rằng có gì đó vừa biến mất.

Đây là kiểu hỏng tệ nhất vì nó trông giống hệt một lần triển khai thành công. Ứng dụng kết nối được, auto.create.topics.enable tạo lại topic, producer ghi tiếp, và mọi thứ chạy — trên một topic rỗng.

volumes:
  - kafka-data:/tmp/kafka-logs

Một dòng. docker compose down không xoá volume có tên, nên dữ liệu sống qua mọi lần triển khai — chỉ docker compose down -v mới xoá, và đó là lý do lệnh đó có chữ -v.

Một chi tiết đi kèm: volume Docker mới tạo thuộc về root, còn tiến trình Kafka trong ảnh apache/kafka chạy dưới appuser (uid 1000). Không sửa quyền thì broker chết ngay ở bước định dạng thư mục với thông báo về bootstrap.checkpoint.tmp — một thông báo không nói gì về quyền. Tôi vấp đúng chuyện này khi dựng phép đo cho bài trước.

Giới hạn bộ nhớ 400 MB giết broker

Ghi 1.000.000 tin × 500 byte:

Kết quả
--memory 400m OOMKilled=true, container biến mất giữa chừng
không giới hạn 574.712 tin/giây, 274 MB/giây

Docker báo OOMKilled=true. Kafka không có dòng log nào — tiến trình bị SIGKILL, không kịp ghi gì.

Phần 2 đo được broker rỗng chỉ tốn 290 MB, nên 400 MB trông có vẻ rộng rãi. Nhưng dưới tải thì bộ đệm producer, bộ đệm mạng và vùng nhớ ngoài heap của JVM cộng lại vượt xa con số đó.

Hai dòng đi cùng nhau:

mem_limit: 6g
environment:
  KAFKA_HEAP_OPTS: "-Xmx4g -Xms4g"

Heap nhỏ hơn hẳn giới hạn container, chừa chỗ cho vùng ngoài heap. Và phần 19 đã đo lý do đừng cấp heap lớn hơn: Kafka dựa vào bộ đệm trang của hệ điều hành, không dựa vào heap của chính nó.

-Xms bằng -Xmx để JVM cấp hết ngay từ đầu — thấy vấn đề bộ nhớ lúc khởi động tốt hơn thấy nó lúc ba giờ sáng.

Tắt sạch nhanh hơn tưởng

974 MB dữ liệu trên đĩa, docker stop:

1,2 giây
log kết thúc bằng "App info kafka.server for 1 unregistered"

Nhanh. Nhưng thời gian chờ mặc định của Docker là 10 giây, và broker lớn với nhiều dữ liệu chưa flush có thể vượt qua. Vượt là SIGKILL, và lần khởi động sau phải chạy khôi phục log — chậm hơn nhiều so với việc chờ thêm vài giây.

stop_grace_period: 120s

Con số này rẻ: nếu broker tắt xong trong 1,2 giây thì Docker đi tiếp ngay, không đợi đủ 120 giây.

advertised.listeners là chỗ hỏng nhiều nhất

Broker trả về địa chỉ này cho client, và client dùng nó cho mọi kết nối sau lần đầu.

KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092

Chạy được trong mạng Docker. Nhưng client ngoài host không phân giải được kafka, nên nó kết nối lần đầu thành công (qua localhost:9092 được ánh xạ) rồi thất bại ở lần thứ hai — với thông báo không nhắc gì tới tên máy.

Cách xử lý cho cả hai loại client là hai listener:

KAFKA_LISTENERS: INTERNAL://:9092,EXTERNAL://:9094,CONTROLLER://:9093
KAFKA_ADVERTISED_LISTENERS: INTERNAL://kafka:9092,EXTERNAL://kafka.cong-ty.vn:9094
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: CONTROLLER:PLAINTEXT,INTERNAL:PLAINTEXT,EXTERNAL:SASL_SSL
KAFKA_INTER_BROKER_LISTENER_NAME: INTERNAL

Đây cũng là cách đúng để bật bảo mật cho client ngoài mà không bắt lưu lượng nội bộ trả giá TLS (phần 34).

Bốn thứ khác cho môi trường thật

restart: unless-stopped. Không có nó, broker chết vì bất kỳ lý do gì sẽ nằm im tới khi có người để ý.

Kiểm tra sức khoẻ phải hỏi Kafka, không hỏi cổng. Cổng 9092 mở từ trước khi broker sẵn sàng — phần 2 đo được ba trạng thái STARTING → RECOVERY → RUNNING, và chỉ ở trạng thái cuối nó mới phục vụ.

healthcheck:
  test: ["CMD-SHELL", "/opt/kafka/bin/kafka-broker-api-versions.sh --bootstrap-server localhost:9092 || exit 1"]
  interval: 30s
  start_period: 60s

Giới hạn kích thước log của Docker. Kafka ghi log rất nhiều, và trình điều khiển json-file mặc định không có giới hạn. Phần 22 đo được một tin hỏng sinh 8,3 triệu ngoại lệ trong 12 giây — nếu mỗi ngoại lệ ghi một dòng, đó là hàng gigabyte.

logging:
  driver: json-file
  options: {max-size: "100m", max-file: "5"}

TZ đúng múi giờ. Dấu thời gian trong log là thứ bạn đọc lúc có sự cố; lệch bảy tiếng làm việc đối chiếu với log ứng dụng thành cực hình.

Nhiều broker trên một máy vật lý là ảo tưởng

Chạy ba container Kafka trên một máy cho replication-factor=3 không cho bạn khả năng chịu lỗi nào. Cùng đĩa, cùng nguồn điện, cùng nhân hệ điều hành. Máy đó chết là cả ba bản sao chết.

Nó có ích để thử nghiệm — phần lớn phép đo trong loạt bài này dựng như vậy — nhưng đừng nhầm với dự phòng thật.

Compose tối thiểu cho một broker thật

services:
  kafka:
    image: apache/kafka:3.9.0
    restart: unless-stopped
    mem_limit: 6g
    stop_grace_period: 120s
    volumes: ["kafka-data:/tmp/kafka-logs"]
    environment:
      KAFKA_NODE_ID: 1
      KAFKA_PROCESS_ROLES: broker,controller
      KAFKA_LISTENERS: PLAINTEXT://:9092,CONTROLLER://:9093
      KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092
      KAFKA_CONTROLLER_QUORUM_VOTERS: 1@kafka:9093
      KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER
      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT
      KAFKA_HEAP_OPTS: "-Xmx4g -Xms4g"
      KAFKA_AUTO_CREATE_TOPICS_ENABLE: "false"
      TZ: Asia/Ho_Chi_Minh
    logging:
      driver: json-file
      options: {max-size: "100m", max-file: "5"}
volumes:
  kafka-data:

KAFKA_AUTO_CREATE_TOPICS_ENABLE: "false" là dòng phần 14 giải thích: topic tạo tự động nhận replication-factor=1, và một lỗi gõ sai tên topic tạo ra một topic không có bản sao nào.

Thử ba mươi giây

Kiểm cụm Docker của bạn có giữ được dữ liệu không:

docker inspect <container> --format '{{range .Mounts}}{{.Type}} {{.Source}} -> {{.Destination}}{{"\n"}}{{end}}'

Không có dòng nào trỏ tới /tmp/kafka-logs (hoặc /var/lib/kafka/data tuỳ ảnh) nghĩa là một lần docker rm sẽ xoá sạch mọi thứ — và không có gì cảnh báo bạn.

Phần sau so Kafka với RabbitMQ: hai công cụ giải hai bài toán khác nhau, và chọn nhầm thì đau ở đâu.