Phần 19 dự đoán rằng bật TLS sẽ tắt sendfile và làm việc đọc đắt hơn hẳn việc ghi. Bài này dựng một broker có cả hai cổng và đo xem dự đoán đó đúng bao nhiêu.

Chi phí TLS ở hai chiều, nguyên nhân, và bốn giao thức

Dựng broker có cả hai cổng

Một broker, hai listener — PLAINTEXT ở 9092 và SSL ở 9094 — nên mọi phép so sánh chạy trên cùng phần cứng, cùng dữ liệu, cùng thời điểm.

KAFKA_LISTENERS=PLAINTEXT://:9092,SSL://:9094,CONTROLLER://:9093
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP=CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT,SSL:SSL
KAFKA_SSL_KEYSTORE_FILENAME=server.keystore.jks
KAFKA_SSL_KEYSTORE_CREDENTIALS=keystore_creds
KAFKA_SSL_KEY_CREDENTIALS=key_creds

Ảnh apache/kafka đọc mật khẩu từ tệp trong /etc/kafka/secrets, không đọc từ biến môi trường. Đặt KAFKA_SSL_KEYSTORE_PASSWORD trực tiếp làm container chết với thông báo line 18: !1: unbound variable — một thông báo không nói gì về nguyên nhân, và tôi mất một lúc mới lần ra.

Ghi: chậm 20%

300.000 tin × 500 byte, ba vòng:

Vòng 1 Vòng 2 Vòng 3
PLAINTEXT 197,58 241,23 279,94 MB/giây
SSL 201,20 190,23 223,87 MB/giây

Lấy vòng đã nóng máy: 279,94 so với 223,87 — SSL chậm 20%.

Đây là chi phí mã hoá thuần tuý. Không nhỏ, nhưng cũng không phải thứ khiến ai đó từ bỏ TLS.

Đọc: chậm 2,2 lần

900.000 tin, ba vòng:

Vòng 1 Vòng 2 Vòng 3
PLAINTEXT 644,7 993,9 1052,4 MB/giây
SSL 491,0 470,6 483,3 MB/giây

1052,4 so với 483,3 — SSL chậm 2,2 lần.

Nhưng con số quan trọng hơn nằm ở hình dạng của hai hàng.

Hàng PLAINTEXT tăng dần: 644 → 993 → 1052. Đó là bộ đệm trang nóng lên (phần 19).

Hàng SSL gần như phẳng: 491 → 470 → 483. Nó không hưởng lợi gì từ việc dữ liệu đã nằm sẵn trong bộ đệm trang.

Vì sao đọc tốn hơn ghi nhiều đến vậy

Phần 19 đã giải thích cơ chế, và phép đo này xác nhận nó.

Không có TLS, broker gọi sendfile: nhân hệ điều hành đẩy thẳng trang từ bộ đệm ra socket. Byte không bao giờ đi vào vùng nhớ của JVM. Đó là lý do một broker heap nhỏ phục vụ được hơn một gigabyte mỗi giây.

Có TLS, mỗi byte phải vào vùng người dùng để mã hoá. Zero-copy biến mất hoàn toàn. Và cùng với nó, phần lớn lợi ích của bộ đệm trang cũng biến mất — vì nút thắt chuyển từ "lấy dữ liệu ra khỏi đĩa" sang "chạy AES trên từng byte". Đó chính là cột SSL phẳng ở trên.

Phía ghi thì không có sendfile để mất: dữ liệu đến từ mạng và phải đi qua JVM dù thế nào. Nên nó chỉ tốn chi phí mã hoá thuần: 20%.

Kết luận thực dụng cho việc lập kế hoạch dung lượng: bật TLS thì phải tính lại phía đọc, không phải phía ghi. Nếu cụm của bạn có nhiều nhóm consumer đọc cùng một dữ liệu, đó là chỗ chi phí nhân lên.

Bắt tay không phải chỗ đáng lo

PLAINTEXT   701 ms cho mỗi lệnh CLI
SSL         704 ms

Chênh lệch nằm dưới nhiễu, và cả hai con số đều là thời gian khởi động JVM (phần 2), không phải thời gian bắt tay.

Bắt tay TLS chỉ xảy ra một lần mỗi kết nối, và client Kafka giữ kết nối rất lâu. Với ứng dụng chạy dài, nó không đáng kể.

Nó chỉ đáng kể với ứng dụng tạo producer mới cho từng yêu cầu — nhưng đó là mẫu sai vì nhiều lý do khác nữa (phần 2).

Bốn giao thức

Mã hoá Xác thực
PLAINTEXT không không
SSL bằng chứng chỉ client (mTLS), nếu bật
SASL_PLAINTEXT không
SASL_SSL

SASL_PLAINTEXT là cái bẫy. Tên nghe như có bảo mật, nhưng nó chỉ xác thực — thông tin xác thực đi trần trên dây. Với SASL/PLAIN thì đó là mật khẩu ở dạng rõ; với SCRAM thì là dữ liệu thách thức, khá hơn nhưng vẫn không có mã hoá cho dữ liệu.

Nó chỉ hợp lý khi có một lớp mã hoá khác bên dưới — ví dụ lưới dịch vụ đã lo mTLS. Trong mọi trường hợp khác, dùng SASL_SSL.

Chọn cơ chế SASL

SASL/SCRAM-SHA-512 — lựa chọn mặc định hợp lý. Thông tin xác thực lưu trong chính Kafka (trước đây là ZooKeeper), thêm bớt user bằng kafka-configs.sh, không cần hạ tầng ngoài.

SASL/GSSAPI (Kerberos) — khi tổ chức đã có Kerberos. Mạnh, và phức tạp tương xứng.

SASL/OAUTHBEARER — khi đã có một nhà cung cấp danh tính. Token có hạn, thu hồi tập trung được — ưu điểm vận hành thật so với mật khẩu tĩnh.

SASL/PLAIN — mật khẩu nằm trong tệp cấu hình của broker, đổi mật khẩu phải khởi động lại. Chỉ dùng cho môi trường thử nghiệm.

mTLS thay vì SASL — danh tính lấy từ chứng chỉ client. Không có mật khẩu nào để rò rỉ, nhưng bạn phải vận hành một hạ tầng chứng chỉ, kể cả việc gia hạn. Chứng chỉ hết hạn lúc 3 giờ sáng là một loại sự cố rất khó chịu.

Phân quyền là bước riêng

Xác thực trả lời "bạn là ai". Phân quyền trả lời "bạn được làm gì" — và Kafka không tự bật nó.

kafka-acls.sh --bootstrap-server kf:9094 --command-config /tmp/ssl.props \
  --add --allow-principal User:doi-phan-tich \
  --operation Read --topic don-hang --group '*'

Không bật authorizer.class.name thì mọi user đã xác thực đều làm được mọi thứ — kể cả xoá topic của người khác. Xác thực mà không phân quyền chỉ ghi lại được ai đã gây ra sự cố, không ngăn được nó.

Và một chi tiết vận hành: bật phân quyền trên cụm đang chạy sẽ chặn hết mọi thứ trừ những gì đã có ACL. Cách chuyển an toàn là bật allow.everyone.if.no.acl.found=true trước, thêm đủ ACL, rồi mới tắt cờ đó.

Nếu chỉ làm được một việc

Với cụm nằm trong mạng riêng và mọi client thuộc cùng một tổ chức, thứ tự ưu tiên thực dụng:

  1. Đảm bảo cụm không lộ ra Internet. Nhóm bảo mật mạng và tường lửa làm việc này rẻ hơn mọi thứ bên dưới.
  2. Bật SASL_SSL với SCRAM. Trả 20% ghi và 2,2 lần đọc.
  3. Bật ACL. Không tốn hiệu năng, và là thứ ngăn một dịch vụ đọc dữ liệu nó không nên đọc.

Bước 3 rẻ nhất và hay bị bỏ qua nhất.

Thử ba mươi giây

Kiểm cụm của bạn đang mở tới đâu:

# giao thức của từng listener
kafka-configs.sh --bootstrap-server kf:9092 --describe --entity-type brokers --entity-name 1 \
  | tr ',' '\n' | grep -iE "listener|security.protocol"

# có bật phân quyền không
kafka-acls.sh --bootstrap-server kf:9092 --list 2>&1 | head -3

Nếu lệnh thứ hai báo không có authorizer, mọi client kết nối được đều có toàn quyền — kể cả xoá topic.

Phần sau đo quy mô cụm: bao nhiêu broker, bao nhiêu partition, và khi nào cần thêm.