Service mesh hứa mTLS, quan sát và điều khiển lưu lượng mà không sửa mã ứng dụng. Bài này cài Linkerd lên cụm thật rồi đo cả cái nó cho lẫn cái nó lấy.

Độ trễ trước và sau khi tiêm sidecar, tài nguyên, và giới hạn của mTLS

Độ trễ: cùng lời gọi, trước và sau

200 yêu cầu mỗi lần, qua ClusterIP, ba lần mỗi cấu hình.

p50 (ms) p95 (ms)
Trước 0,14 / 0,13 / 0,13 0,22 / 0,21 / 0,17
Sau 0,31 / 0,31 / 0,36 0,48 / 0,44 / 0,66

Chậm hơn khoảng 0,19 ms — gần 2,4 lần.

Con số tương đối nghe đáng sợ, con số tuyệt đối thì không. Đó là hai chặng proxy: một ở pod gọi, một ở pod nhận. Với một dịch vụ HTTP thật, thời gian xử lý thường tính bằng mili giây, nên 0,19 ms là dưới 5%.

Nhưng với một chuỗi mười dịch vụ gọi nhau, nó thành gần 2 ms cộng dồn — và với dịch vụ nội bộ chỉ tra một khoá trong bộ nhớ đệm, nó có thể nhân đôi thời gian phản hồi. Ranh giới nằm ở chỗ dịch vụ của bạn nhanh cỡ nào, không nằm ở mesh.

Tài nguyên: nhẹ hơn tôi tưởng

linkerd-proxy trong pod   3-5 Mi RAM, 1m CPU mỗi cái
control plane (3 pod)     83 Mi tổng

Tôi vào bài này với ấn tượng "sidecar tốn hàng trăm MB". Với Linkerd thì không: proxy viết bằng Rust và đo được 3–5 MB.

Nhưng đừng suy ra cho mọi mesh. Các mesh dựa trên Envoy nặng hơn đáng kể — đó là điều tôi không đo trong bài này, và đo trên một cụm rỗng thì cũng không nói được gì về lúc tải cao. Con số 5 MB đúng cho Linkerd, ở trạng thái nhàn rỗi, trên cụm này.

Chi phí thật hơn không phải RAM mà là số lượng: mỗi pod thành hai container. 500 pod là 500 proxy phải nâng cấp, và nâng cấp mesh nghĩa là khởi động lại toàn bộ pod trong cụm.

Hai chi tiết gặp lại từ các phần trước

Proxy chạy như sidecar nativeinitContainers với restartPolicy: Always, đúng cơ chế đo ở phần 36. Nhờ vậy nó khởi động trước ứng dụng và dừng sau, nên không còn cảnh Job có mesh thì không bao giờ kết thúc.

Và ảnh proxy là distroless:

kubectl exec ... -c linkerd-proxy -- /bin/sh
-> stat /bin/sh: no such file or directory

Đúng tình huống ở phần 42. Muốn nhìn vào trong nó thì dùng kubectl debug --target.

mTLS bật sẵn, không khai gì cả

Đọc thẳng metric của proxy trong pod gọi:

tcp_open_total{direction="outbound", tls="true",
               server_id="default.mesh.serviceaccount.identity..."}

tls="true" và có danh tính của phía bên kia. Không có tệp cấu hình nào, không có chứng chỉ nào tôi phải tạo. Đây đúng là thứ mesh bán, và nó giao đúng hàng.

Danh tính đó dựa trên ServiceAccount — chính thứ đo ở phần 33. Mesh dùng lại token đó để mỗi bên chứng minh mình là ai.

Nhưng cài mesh không tự nó chặn gì

Tôi tạo một pod ở namespace không có mesh rồi gọi vào Service trong mesh:

p50 0,24 ms, không lỗi

Chạy bình thường. Không mã hoá, không danh tính, không ai hỏi gì.

Đây là điểm quan trọng nhất của bài. mTLS của mesh là cơ hội: hai bên đều có proxy thì mã hoá, một bên không có thì đi thẳng bằng văn bản rõ. Cài mesh xong rồi báo cáo "lưu lượng nội bộ đã được mã hoá" là sai, trừ khi bạn thêm chính sách bắt buộc.

Với Linkerd đó là Server + AuthorizationPolicy; với Istio là PeerAuthentication ở chế độ STRICT. Cả hai đều là bước riêng, không tự bật.

Và điều đó hợp lý: bật cưỡng chế ngay lúc cài là cắt đứt mọi thứ chưa được tiêm — bộ giám sát, job cũ, dịch vụ ngoài cụm. Nhưng "hợp lý về vận hành" và "an toàn ngay sau khi cài" là hai chuyện khác nhau, và người ta hay nhầm.

Vậy có nên dùng không

Mesh đáng giá khi bạn cần ít nhất hai trong ba thứ sau, trên nhiều dịch vụ:

  • mTLS giữa các dịch vụ, có danh tính, không sửa mã.
  • Quan sát đồng nhất: tỷ lệ thành công, độ trễ, đồ thị phụ thuộc — không cần mỗi đội tự gắn thư viện.
  • Điều khiển lưu lượng: chia phần trăm, thử lại, ngắt mạch, phát hành từng phần.

Chỉ cần một trong ba thì thường có cách rẻ hơn: NetworkPolicy cho phân tách (phần 19), thư viện đo sẵn cho quan sát, Ingress cho chia lưu lượng ở biên.

Và cái giá ít ai tính trước: mesh là một tầng nữa phải gỡ rối. Khi có sự cố, câu hỏi "lỗi ở ứng dụng hay ở proxy" sẽ xuất hiện mỗi lần. Với ba dịch vụ thì cái giá đó lớn hơn lợi ích; với ba trăm thì ngược lại.

Nếu quyết định dùng

Ba điều đo được trong bài gợi ra thứ tự triển khai:

  1. Tiêm dần theo namespace, đừng tiêm cả cụm một lượt. Nhãn linkerd.io/inject=enabled đặt ở namespace và chỉ có tác dụng khi pod được tạo lại — nên bạn kiểm soát được nhịp độ.
  2. Đo độ trễ trước và sau cho đúng dịch vụ của bạn. 0,19 ms ở đây không phải con số của bạn.
  3. Bật cưỡng chế mTLS sau cùng, khi đã chắc mọi thứ cần nói chuyện đều đã có proxy. Trước đó, đừng nói với ai rằng lưu lượng đã được mã hoá.

Thử ba mươi giây

Nếu đã có mesh, kiểm xem lưu lượng thật sự được mã hoá bao nhiêu phần:

kubectl exec <pod-co-mesh> -c <container-ung-dung> -- \
  python3 -c "
import urllib.request
d = urllib.request.urlopen('http://127.0.0.1:4191/metrics', timeout=5).read().decode()
co = khong = 0
for l in d.split('\n'):
    if 'tcp_open_total' not in l or 'outbound' not in l: continue
    n = float(l.rsplit(' ', 1)[-1] or 0)
    if 'tls=\"true\"' in l: co += n
    elif 'loopback' not in l: khong += n
print(f'ma hoa {co:.0f} / khong ma hoa {khong:.0f}')
"

Cột thứ hai lớn hơn 0 nghĩa là có luồng đang đi bằng văn bản rõ — thường là tới một dịch vụ chưa được tiêm mà không ai để ý.

Phần sau: chạy cơ sở dữ liệu trong Kubernetes — đo, và nói rõ khi nào không nên.