Cloud & hạ tầng

Cách Kubernetes CRI xử lý exec, attach và chuyển tiếp cổng

Một bài phân tích sâu năm 2024 giải thích kiến trúc truyền phát dựa trên URL độc đáo đằng sau các lệnh của Container Runtime Interface (CRI) trong Kubernetes.

Diagram illustrating the separate control and data paths in Kubernetes CRI streaming
Hình ảnh: Kubernetes Blog, giấy phép CC BY 4.0

Được dịch tự động từ bản gốc tiếng Anh.

Trong một bài đăng trên Kubernetes Blog vào tháng 5 năm 2024, Sascha Grunert đã trình bày chi tiết về cơ chế nội bộ của ba cuộc gọi thủ tục từ xa (RPC) cụ thể của Container Runtime Interface (CRI). Bài viết giải thích cách Exec, Attach và PortForward khác biệt so với các tương tác gRPC tiêu chuẩn bằng việc sử dụng mô hình truyền phát dựa trên URL riêng biệt, vốn vẫn giữ nguyên kể từ khi được thiết kế vào năm 2016.

Chuyện gì đã xảy ra

Kubernetes CRI đóng vai trò là cầu nối chính giữa kubelet và các runtime container, yêu cầu các runtime phải cung cấp một máy chủ gRPC tuân theo giao diện Protocol Buffer đã định nghĩa. Trong khi hầu hết các hoạt động của CRI dựa vào các cuộc gọi đơn giản (unary calls) hoặc truyền phát phía máy chủ, Grunert nhấn mạnh rằng Exec, Attach và PortForward hoạt động khác biệt. Ba chức năng này rất quan trọng đối với các nhà phát triển cần chạy lệnh bên trong container, xem đầu ra trực tiếp hoặc chuyển tiếp cổng mạng để gỡ lỗi.

Grunert đã truy ngược lịch sử của các tính năng này về một tài liệu thiết kế năm 2016, trước cả các Đề xuất Nâng cao Kubernetes (Kubernetes Enhancement Proposals) hiện đại. Trước sáng kiến CRI, các khả năng này bị ràng buộc chặt chẽ với những runtime cụ thể như Docker hoặc rkt. Cộng đồng đã cân nhắc việc triển khai truyền phát RPC gốc nhưng bác bỏ vì nó sẽ tạo ra nút thắt cổ chai mạng trong kubelet và hạn chế tính linh hoạt của runtime. Thay vào đó, họ áp dụng mô hình mà runtime cung cấp một máy chủ truyền phát, cho phép mỗi triển khai quản lý kết nối một cách độc lập.

Quyết định kiến trúc này có nghĩa là mặc dù yêu cầu ban đầu đi qua giao diện gRPC tiêu chuẩn, quá trình truyền dữ liệu thực tế diễn ra qua một kết nối HTTP riêng biệt. Sự tách biệt này cho phép các runtime phát triển các triển khai truyền phát của chúng mà không cần sửa đổi định nghĩa CRI cốt lõi. Mặc dù một số cải tiến nhỏ đã được hợp nhất qua nhiều năm, mẫu cơ bản gồm việc yêu cầu một URL rồi kết nối trực tiếp đến URL đó vẫn không thay đổi.

Cách thức hoạt động

Quá trình bắt đầu khi một client, chẳng hạn như kubectl hoặc crictl, gửi một yêu cầu gRPC tới runtime cho phiên Exec, Attach hoặc PortForward. Khác với các cuộc gọi API điển hình trả về dữ liệu trực tiếp, runtime xác thực yêu cầu và lưu trữ nó trong bộ nhớ đệm theo dõi kết nối. Sau đó, nó trả về phản hồi chỉ chứa một URL đầy đủ. Client phải kết nối tới URL này, nâng cấp kết nối để sử dụng giao thức SPDY hoặc ngày càng phổ biến hơn là WebSockets, để bắt đầu truyền phát dữ liệu.

Figure from the original article: Cách Kubernetes CRI xử lý exec, attach và chuyển tiếp cổng
Hình từ bài viết gốc · Kubernetes Blog · CC BY 4.0

Đối với Exec và Attach, Kubernetes định nghĩa một giao thức cụ thể với năm phiên bản, hiện tại lên đến v5.channel.k8s.io. Giao thức này sử dụng byte đầu tiên của mỗi gói tin để xác định loại luồng, chẳng hạn như đầu vào chuẩn (stdin), đầu ra chuẩn (stdout), lỗi chuẩn (stderr) hoặc các tín hiệu điều khiển như thay đổi kích thước terminal và đóng kết nối. Mã nguồn của kubelet cung cấp một thư viện tái sử dụng để xử lý việc diễn giải giao thức này, yêu cầu các runtime chỉ cần triển khai logic để thực thi lệnh hoặc gắn vào quy trình. PortForward hoạt động khác biệt vì nó thiếu định nghĩa giao thức nghiêm ngặt. Thay vào đó, runtime đi vào namespace mạng của container và truyền phát các khung SPDY thô, dựa vào các thư viện như moby/spdystream để quản lý luồng dữ liệu.

Chi tiết chính

  • Các RPC Exec, Attach và PortForward của CRI chỉ trả về một chuỗi URL trong phản hồi, không phải luồng dữ liệu thực tế.
  • Client phải nâng cấp kết nối HTTP lên SPDY hoặc WebSockets để thiết lập phiên truyền phát sau khi nhận được URL.
  • Các giao thức Exec và Attach sử dụng byte đầu tiên của mỗi gói tin để phân biệt giữa stdin, stdout, stderr, lỗi, sự kiện thay đổi kích thước và tín hiệu đóng kết nối.
  • Có năm phiên bản của giao thức lệnh từ xa, trong đó v5 bổ sung hỗ trợ tín hiệu CLOSE cho WebSockets.
  • Kubelet cung cấp một thư viện tái sử dụng với giao diện Runtime mà các runtime phải triển khai để xử lý việc thực thi lệnh cơ bản và đi vào namespace mạng.
  • Các nỗ lực trong tương lai tập trung vào việc thay thế SPDY bằng WebSockets, với các công cụ như crictl v1.30 đã hỗ trợ cờ --transport để lựa chọn giữa chúng.

Tại sao điều này quan trọng

Đối với các kỹ sư phần mềm xây dựng hoặc duy trì runtime container, việc hiểu rõ kiến trúc này là yếu tố then chốt để triển khai đúng đắn. Việc tách rời mặt phẳng điều khiển (gRPC) khỏi mặt phẳng dữ liệu (truyền phát HTTP) có nghĩa là các nhà phát triển runtime không thể đơn thuần coi những cuộc gọi này là các yêu cầu API tiêu chuẩn. Họ phải quản lý các phiên truyền phát đồng thời, xử lý việc nâng cấp giao thức và đảm bảo tính tương thích với nhiều phiên bản giao thức. Việc diễn giải sai byte đầu tiên của gói dữ liệu hoặc không hỗ trợ các sự kiện thay đổi kích thước terminal có thể dẫn đến trải nghiệm người dùng bị hỏng trong các quy trình làm việc phát triển thông thường.

Thiết kế này cũng ảnh hưởng đến cách các công cụ gỡ lỗi tương tác với cụm. Vì dữ liệu bỏ qua kubelet sau khi lấy URL ban đầu, các chính sách mạng và proxy phải cho phép giao tiếp trực tiếp giữa client và node lưu trữ container. Khi hệ sinh thái chuyển sang WebSockets, các kỹ sư phải đảm bảo công cụ của họ hỗ trợ lớp vận chuyển mới. Công việc đang diễn ra trong các dự án như CRI-O, nơi di chuyển logic truyền phát sang conmon-rs, minh họa cách tính linh hoạt này cho phép các runtime giữ cho phiên hoạt động ngay cả khi quy trình runtime chính khởi động lại, cải thiện độ tin cậy cho các phiên gỡ lỗi chạy dài.

Bạn có thể làm gì

  • Xác minh rằng runtime container của bạn hỗ trợ giao thức v5.channel.k8s.io mới nhất để đảm bảo xử lý đúng các tín hiệu đóng luồng.
  • Cập nhật các công cụ client như crictl lên phiên bản 1.30 trở lên để tận dụng các tính năng mới.
  • Kiểm tra rằng các chính sách mạng và proxy của bạn cho phép giao tiếp trực tiếp giữa client và node chứa container.
  • Đảm bảo công cụ của bạn hỗ trợ lớp vận chuyển WebSockets nếu môi trường của bạn đang chuyển đổi từ SPDY.
  • Tìm hiểu về cách các runtime như CRI-O sử dụng conmon-rs để duy trì tính ổn định của phiên trong các tình huống khởi động lại quy trình.

Công cụ từ cửa hàng Bytechap

Đọc tiếp

Tất cả bài viết