Cân bằng tải và mở rộng quy mô các kết nối tồn tại lâu dài trong Kubernetes

Cân bằng tải và mở rộng quy mô các kết nối tồn tại lâu dài trong Kubernetes
Bài viết này sẽ giúp bạn hiểu cách hoạt động của cân bằng tải trong Kubernetes, điều gì xảy ra khi mở rộng các kết nối tồn tại lâu dài và lý do bạn nên cân nhắc cân bằng phía máy khách nếu sử dụng HTTP/2, gRPC, RSockets, AMQP hoặc các giao thức tồn tại lâu dài khác . 

Một chút về cách phân phối lại lưu lượng truy cập trong Kubernetes 

Kubernetes cung cấp hai cách trừu tượng thuận tiện để triển khai ứng dụng: Dịch vụ và Triển khai.

Triển khai mô tả cách thức và số lượng bản sao ứng dụng của bạn sẽ chạy tại bất kỳ thời điểm nào. Mỗi ứng dụng được triển khai dưới dạng Pod và được gán một địa chỉ IP.

Các dịch vụ có chức năng tương tự như bộ cân bằng tải. Chúng được thiết kế để phân phối lưu lượng truy cập trên nhiều nhóm.

Hãy xem nó trông như thế nào.

  1. Trong sơ đồ bên dưới, bạn có thể thấy ba phiên bản của cùng một ứng dụng và một bộ cân bằng tải:

    Cân bằng tải và mở rộng quy mô các kết nối tồn tại lâu dài trong Kubernetes

  2. Bộ cân bằng tải được gọi là Dịch vụ và được gán một địa chỉ IP. Mọi yêu cầu gửi đến đều được chuyển hướng đến một trong các nhóm:

    Cân bằng tải và mở rộng quy mô các kết nối tồn tại lâu dài trong Kubernetes

  3. Kịch bản triển khai xác định số lượng phiên bản của ứng dụng. Bạn sẽ gần như không bao giờ phải mở rộng trực tiếp theo:

    Cân bằng tải và mở rộng quy mô các kết nối tồn tại lâu dài trong Kubernetes

  4. Mỗi nhóm được gán địa chỉ IP riêng:

    Cân bằng tải và mở rộng quy mô các kết nối tồn tại lâu dài trong Kubernetes

Sẽ rất hữu ích khi coi các dịch vụ như một tập hợp các địa chỉ IP. Mỗi lần bạn truy cập dịch vụ, một trong các địa chỉ IP sẽ được chọn từ danh sách và được sử dụng làm địa chỉ đích.

Nó trông như thế này.

  1. Một yêu cầu 10.96.45.152 được nhận tới dịch vụ:

    Cân bằng tải và mở rộng quy mô các kết nối tồn tại lâu dài trong Kubernetes

  2. Dịch vụ chọn một trong ba địa chỉ nhóm làm đích:

    Cân bằng tải và mở rộng quy mô các kết nối tồn tại lâu dài trong Kubernetes

  3. Lưu lượng truy cập được chuyển hướng đến một nhóm cụ thể:

    Cân bằng tải và mở rộng quy mô các kết nối tồn tại lâu dài trong Kubernetes

Nếu ứng dụng của bạn bao gồm giao diện người dùng và phụ trợ thì bạn sẽ có cả dịch vụ và triển khai cho mỗi giao diện.

Khi giao diện người dùng đưa ra yêu cầu tới phần phụ trợ, nó không cần biết chính xác có bao nhiêu nhóm mà phần phụ trợ phục vụ: có thể có một, mười hoặc một trăm.

Ngoài ra, giao diện người dùng không biết gì về địa chỉ của các nhóm phục vụ phần phụ trợ.

Khi giao diện người dùng đưa ra yêu cầu tới phần phụ trợ, nó sẽ sử dụng địa chỉ IP của dịch vụ phụ trợ và địa chỉ này không thay đổi.

Dưới đây là cách có vẻ.

  1. Dưới 1 yêu cầu thành phần phụ trợ nội bộ. Thay vì chọn một cái cụ thể cho phần phụ trợ, nó sẽ đưa ra yêu cầu tới dịch vụ:

    Cân bằng tải và mở rộng quy mô các kết nối tồn tại lâu dài trong Kubernetes

  2. Dịch vụ chọn một trong các nhóm phụ trợ làm địa chỉ đích:

    Cân bằng tải và mở rộng quy mô các kết nối tồn tại lâu dài trong Kubernetes

  3. Lưu lượng truy cập đi từ Pod 1 đến Pod 5, được lựa chọn bởi dịch vụ:

    Cân bằng tải và mở rộng quy mô các kết nối tồn tại lâu dài trong Kubernetes

  4. Dưới 1 không biết chính xác có bao nhiêu nhóm như dưới 5 ẩn đằng sau dịch vụ:

    Cân bằng tải và mở rộng quy mô các kết nối tồn tại lâu dài trong Kubernetes

Nhưng chính xác thì dịch vụ phân phối yêu cầu như thế nào? Có vẻ như cân bằng vòng tròn được sử dụng? Hãy tìm ra nó. 

Cân bằng trong dịch vụ Kubernetes

Dịch vụ Kubernetes không tồn tại. Không có quy trình nào cho dịch vụ được gán địa chỉ IP và cổng.

Bạn có thể xác minh điều này bằng cách đăng nhập vào bất kỳ nút nào trong cụm và chạy lệnh netstat -ntlp.

Bạn thậm chí sẽ không thể tìm thấy địa chỉ IP được cấp cho dịch vụ.

Địa chỉ IP của dịch vụ nằm trong lớp điều khiển, trong bộ điều khiển và được ghi trong cơ sở dữ liệu - etcd. Địa chỉ tương tự được sử dụng bởi một thành phần khác - kube-proxy.
Kube-proxy nhận danh sách địa chỉ IP cho tất cả các dịch vụ và tạo một bộ quy tắc iptables trên mỗi nút trong cụm.

Các quy tắc này cho biết: “Nếu chúng tôi thấy địa chỉ IP của dịch vụ, chúng tôi cần sửa đổi địa chỉ đích của yêu cầu và gửi nó đến một trong các nhóm”.

Địa chỉ IP dịch vụ chỉ được sử dụng làm điểm vào và không được phục vụ bởi bất kỳ quy trình nào đang nghe địa chỉ IP và cổng đó.

Hãy nhìn vào cái này

  1. Hãy xem xét một cụm gồm ba nút. Mỗi nút có nhóm:

    Cân bằng tải và mở rộng quy mô các kết nối tồn tại lâu dài trong Kubernetes

  2. Vỏ buộc sơn màu be là một phần của dịch vụ. Bởi vì dịch vụ không tồn tại dưới dạng một quy trình nên nó được hiển thị bằng màu xám:

    Cân bằng tải và mở rộng quy mô các kết nối tồn tại lâu dài trong Kubernetes

  3. Nhóm đầu tiên yêu cầu một dịch vụ và phải đi đến một trong các nhóm được liên kết:

    Cân bằng tải và mở rộng quy mô các kết nối tồn tại lâu dài trong Kubernetes

  4. Nhưng dịch vụ không tồn tại, quy trình không tồn tại. Làm thế nào nó hoạt động?

    Cân bằng tải và mở rộng quy mô các kết nối tồn tại lâu dài trong Kubernetes

  5. Trước khi yêu cầu rời khỏi nút, nó sẽ trải qua các quy tắc iptables:

    Cân bằng tải và mở rộng quy mô các kết nối tồn tại lâu dài trong Kubernetes

  6. Các quy tắc iptables biết rằng dịch vụ không tồn tại và thay thế địa chỉ IP của nó bằng một trong các địa chỉ IP của nhóm được liên kết với dịch vụ đó:

    Cân bằng tải và mở rộng quy mô các kết nối tồn tại lâu dài trong Kubernetes

  7. Yêu cầu nhận địa chỉ IP hợp lệ làm địa chỉ đích và được xử lý bình thường:

    Cân bằng tải và mở rộng quy mô các kết nối tồn tại lâu dài trong Kubernetes

  8. Tùy thuộc vào cấu trúc liên kết mạng, yêu cầu cuối cùng sẽ đến được nhóm:

    Cân bằng tải và mở rộng quy mô các kết nối tồn tại lâu dài trong Kubernetes

iptables có thể cân bằng tải không?

Không, iptables được sử dụng để lọc và không được thiết kế để cân bằng.

Tuy nhiên, có thể viết một bộ quy tắc hoạt động như bộ cân bằng giả.

Và đây chính xác là những gì được triển khai trong Kubernetes.

Nếu bạn có ba nhóm, kube-proxy sẽ viết các quy tắc sau:

  1. Chọn phụ đầu tiên với xác suất là 33%, nếu không thì chuyển sang quy tắc tiếp theo.
  2. Chọn cái thứ hai có xác suất là 50%, nếu không thì chuyển sang quy tắc tiếp theo.
  3. Chọn cái thứ ba bên dưới.

Hệ thống này dẫn đến việc mỗi nhóm được chọn với xác suất là 33%.

Cân bằng tải và mở rộng quy mô các kết nối tồn tại lâu dài trong Kubernetes

Và không có gì đảm bảo rằng Nhóm 2 sẽ được chọn tiếp theo sau Nhóm 1.

Ghi: iptables sử dụng mô-đun thống kê với phân phối ngẫu nhiên. Vì vậy, thuật toán cân bằng dựa trên lựa chọn ngẫu nhiên.

Bây giờ bạn đã hiểu cách thức hoạt động của các dịch vụ, hãy xem xét các tình huống dịch vụ thú vị hơn.

Các kết nối tồn tại lâu dài trong Kubernetes không mở rộng quy mô theo mặc định

Mỗi yêu cầu HTTP từ giao diện người dùng đến phần phụ trợ được cung cấp bởi một kết nối TCP riêng biệt, được mở và đóng.

Nếu giao diện người dùng gửi 100 yêu cầu mỗi giây đến phần phụ trợ thì 100 kết nối TCP khác nhau sẽ được mở và đóng.

Bạn có thể giảm thời gian xử lý và tải yêu cầu bằng cách mở một kết nối TCP và sử dụng kết nối đó cho tất cả các yêu cầu HTTP tiếp theo.

Giao thức HTTP có một tính năng gọi là duy trì HTTP hoặc tái sử dụng kết nối. Trong trường hợp này, một kết nối TCP duy nhất được sử dụng để gửi và nhận nhiều yêu cầu và phản hồi HTTP:

Cân bằng tải và mở rộng quy mô các kết nối tồn tại lâu dài trong Kubernetes

Tính năng này không được bật theo mặc định: cả máy chủ và máy khách đều phải được cấu hình tương ứng.

Bản thân việc thiết lập rất đơn giản và có thể truy cập được đối với hầu hết các ngôn ngữ và môi trường lập trình.

Dưới đây là một số liên kết đến các ví dụ bằng các ngôn ngữ khác nhau:

Điều gì xảy ra nếu chúng ta sử dụng tính năng duy trì hoạt động trong dịch vụ Kubernetes?
Giả sử rằng cả giao diện người dùng và phụ trợ đều duy trì hoạt động.

Chúng tôi có một bản sao của giao diện người dùng và ba bản sao của phần phụ trợ. Giao diện người dùng thực hiện yêu cầu đầu tiên và mở kết nối TCP tới phần phụ trợ. Yêu cầu đến dịch vụ, một trong các nhóm phụ trợ được chọn làm địa chỉ đích. Phần phụ trợ sẽ gửi phản hồi và giao diện người dùng sẽ nhận được phản hồi đó.

Không giống như tình huống thông thường khi kết nối TCP bị đóng sau khi nhận được phản hồi, giờ đây nó được giữ mở cho các yêu cầu HTTP tiếp theo.

Điều gì xảy ra nếu giao diện người dùng gửi nhiều yêu cầu hơn đến phần phụ trợ?

Để chuyển tiếp các yêu cầu này, một kết nối TCP mở sẽ được sử dụng, tất cả các yêu cầu sẽ chuyển đến cùng một chương trình phụ trợ nơi yêu cầu đầu tiên được gửi đến.

IPtables có nên phân phối lại lưu lượng truy cập không?

Không phải trong trường hợp này.

Khi một kết nối TCP được tạo, nó sẽ đi qua các quy tắc iptables, trong đó chọn một phần phụ trợ cụ thể nơi lưu lượng truy cập sẽ đi.

Vì tất cả các yêu cầu tiếp theo đều nằm trên kết nối TCP đã mở nên quy tắc iptables không còn được gọi nữa.

Hãy xem nó trông như thế nào.

  1. Nhóm đầu tiên gửi yêu cầu đến dịch vụ:

    Cân bằng tải và mở rộng quy mô các kết nối tồn tại lâu dài trong Kubernetes

  2. Bạn đã biết điều gì sẽ xảy ra tiếp theo. Dịch vụ này không tồn tại, nhưng có các quy tắc iptables sẽ xử lý yêu cầu:

    Cân bằng tải và mở rộng quy mô các kết nối tồn tại lâu dài trong Kubernetes

  3. Một trong các nhóm phụ trợ sẽ được chọn làm địa chỉ đích:

    Cân bằng tải và mở rộng quy mô các kết nối tồn tại lâu dài trong Kubernetes

  4. Yêu cầu đạt đến nhóm. Tại thời điểm này, kết nối TCP liên tục giữa hai nhóm sẽ được thiết lập:

    Cân bằng tải và mở rộng quy mô các kết nối tồn tại lâu dài trong Kubernetes

  5. Mọi yêu cầu tiếp theo từ nhóm đầu tiên sẽ đi qua kết nối đã được thiết lập:

    Cân bằng tải và mở rộng quy mô các kết nối tồn tại lâu dài trong Kubernetes

Kết quả là thời gian phản hồi nhanh hơn và thông lượng cao hơn, nhưng bạn mất khả năng mở rộng quy mô phụ trợ.

Ngay cả khi bạn có hai nhóm ở phần phụ trợ, với kết nối liên tục, lưu lượng truy cập sẽ luôn hướng đến một trong số đó.

Điều này có thể được sửa chữa?

Vì Kubernetes không biết cách cân bằng các kết nối liên tục nên nhiệm vụ này thuộc về bạn.

Dịch vụ là tập hợp các địa chỉ IP và cổng được gọi là điểm cuối.

Ứng dụng của bạn có thể nhận danh sách các điểm cuối từ dịch vụ và quyết định cách phân phối yêu cầu giữa chúng. Bạn có thể mở một kết nối liên tục tới từng nhóm và cân bằng các yêu cầu giữa các kết nối này bằng cách sử dụng vòng tròn.

Hoặc áp dụng thêm thuật toán cân bằng phức tạp.

Mã phía máy khách chịu trách nhiệm cân bằng phải tuân theo logic sau:

  1. Nhận danh sách các điểm cuối từ dịch vụ.
  2. Mở một kết nối liên tục cho mỗi điểm cuối.
  3. Khi cần thực hiện một yêu cầu, hãy sử dụng một trong các kết nối mở.
  4. Thường xuyên cập nhật danh sách điểm cuối, tạo điểm cuối mới hoặc đóng các kết nối liên tục cũ nếu danh sách thay đổi.

Nó sẽ trông như thế này.

  1. Thay vì nhóm đầu tiên gửi yêu cầu đến dịch vụ, bạn có thể cân bằng các yêu cầu ở phía máy khách:

    Cân bằng tải và mở rộng quy mô các kết nối tồn tại lâu dài trong Kubernetes

  2. Bạn cần viết mã hỏi nhóm nào là một phần của dịch vụ:

    Cân bằng tải và mở rộng quy mô các kết nối tồn tại lâu dài trong Kubernetes

  3. Khi bạn có danh sách, hãy lưu nó ở phía máy khách và sử dụng nó để kết nối với nhóm:

    Cân bằng tải và mở rộng quy mô các kết nối tồn tại lâu dài trong Kubernetes

  4. Bạn chịu trách nhiệm về thuật toán cân bằng tải:

    Cân bằng tải và mở rộng quy mô các kết nối tồn tại lâu dài trong Kubernetes

Bây giờ câu hỏi được đặt ra: vấn đề này chỉ áp dụng cho việc duy trì HTTP phải không?

Cân bằng tải phía máy khách

HTTP không phải là giao thức duy nhất có thể sử dụng các kết nối TCP liên tục.

Nếu ứng dụng của bạn sử dụng cơ sở dữ liệu thì kết nối TCP sẽ không được mở mỗi khi bạn cần thực hiện yêu cầu hoặc truy xuất tài liệu từ cơ sở dữ liệu. 

Thay vào đó, kết nối TCP liên tục tới cơ sở dữ liệu sẽ được mở và sử dụng.

Nếu cơ sở dữ liệu của bạn được triển khai trên Kubernetes và quyền truy cập được cung cấp dưới dạng dịch vụ thì bạn sẽ gặp phải các vấn đề tương tự được mô tả trong phần trước.

Một bản sao cơ sở dữ liệu sẽ được tải nhiều hơn những bản sao khác. Kube-proxy và Kubernetes sẽ không giúp cân bằng kết nối. Bạn phải cẩn thận để cân bằng các truy vấn vào cơ sở dữ liệu của mình.

Tùy thuộc vào thư viện bạn sử dụng để kết nối với cơ sở dữ liệu, bạn có thể có các tùy chọn khác nhau để giải quyết vấn đề này.

Dưới đây là ví dụ về cách truy cập cụm cơ sở dữ liệu MySQL từ Node.js:

var mysql = require('mysql');
var poolCluster = mysql.createPoolCluster();

var endpoints = /* retrieve endpoints from the Service */

for (var [index, endpoint] of endpoints) {
  poolCluster.add(`mysql-replica-${index}`, endpoint);
}

// Make queries to the clustered MySQL database

Có nhiều giao thức khác sử dụng kết nối TCP liên tục:

  • WebSockets và WebSockets được bảo mật
  • HTTP / 2
  • gRPC
  • Ổ cắm RS
  • AMQP

Bạn hẳn đã quen thuộc với hầu hết các giao thức này.

Nhưng nếu các giao thức này phổ biến đến vậy thì tại sao lại không có giải pháp cân bằng tiêu chuẩn hóa? Tại sao logic máy khách cần thay đổi? Có giải pháp Kubernetes nguyên gốc không?

Kube-proxy và iptables được thiết kế để đáp ứng hầu hết các trường hợp sử dụng phổ biến khi triển khai lên Kubernetes. Điều này là để thuận tiện.

Nếu bạn đang sử dụng dịch vụ web có API REST thì bạn thật may mắn - trong trường hợp này, các kết nối TCP liên tục không được sử dụng, bạn có thể sử dụng bất kỳ dịch vụ Kubernetes nào.

Nhưng một khi bạn bắt đầu sử dụng các kết nối TCP liên tục, bạn sẽ phải tìm ra cách phân bổ tải đồng đều trên các chương trình phụ trợ. Kubernetes không chứa các giải pháp làm sẵn cho trường hợp này.

Tuy nhiên, chắc chắn có những lựa chọn có thể giúp ích.

Cân bằng các kết nối lâu dài trong Kubernetes

Có bốn loại dịch vụ trong Kubernetes:

  1. Cụm IP
  2. Cổng nút
  3. Cân bằng tải
  4. Không đầu

Ba dịch vụ đầu tiên hoạt động dựa trên địa chỉ IP ảo, được kube-proxy sử dụng để xây dựng các quy tắc iptables. Nhưng nền tảng cơ bản của mọi dịch vụ là dịch vụ không có đầu người.

Dịch vụ headless không có bất kỳ địa chỉ IP nào được liên kết với nó và chỉ cung cấp cơ chế truy xuất danh sách các địa chỉ IP và cổng của nhóm (điểm cuối) được liên kết với nó.

Tất cả các dịch vụ đều dựa trên dịch vụ không đầu.

Dịch vụ ClusterIP là một dịch vụ không có đầu với một số bổ sung: 

  1. Lớp quản lý gán cho nó một địa chỉ IP.
  2. Kube-proxy tạo ra các quy tắc iptables cần thiết.

Bằng cách này, bạn có thể bỏ qua kube-proxy và trực tiếp sử dụng danh sách điểm cuối thu được từ dịch vụ không đầu để cân bằng tải cho ứng dụng của mình.

Nhưng làm cách nào chúng ta có thể thêm logic tương tự vào tất cả các ứng dụng được triển khai trong cụm?

Nếu ứng dụng của bạn đã được triển khai thì nhiệm vụ này có vẻ như không thể thực hiện được. Tuy nhiên, có một lựa chọn thay thế.

Service Mesh sẽ giúp bạn

Có thể bạn đã nhận thấy rằng chiến lược cân bằng tải phía máy khách khá chuẩn.

Khi ứng dụng khởi động, nó:

  1. Nhận danh sách địa chỉ IP từ dịch vụ.
  2. Mở và duy trì một nhóm kết nối.
  3. Cập nhật nhóm định kỳ bằng cách thêm hoặc xóa điểm cuối.

Khi ứng dụng muốn thực hiện một yêu cầu, nó sẽ:

  1. Chọn một kết nối khả dụng bằng cách sử dụng một số logic (ví dụ: vòng tròn).
  2. Thực hiện yêu cầu.

Các bước này áp dụng cho cả kết nối WebSockets, gRPC và AMQP.

Bạn có thể tách logic này thành một thư viện riêng và sử dụng nó trong các ứng dụng của mình.

Tuy nhiên, bạn có thể sử dụng các lưới dịch vụ như Istio hoặc Linkerd để thay thế.

Service Mesh tăng cường ứng dụng của bạn bằng một quy trình:

  1. Tự động tìm kiếm địa chỉ IP dịch vụ.
  2. Kiểm tra các kết nối như WebSockets và gRPC.
  3. Cân bằng các yêu cầu bằng cách sử dụng đúng giao thức.

Service Mesh giúp quản lý lưu lượng trong cụm nhưng khá tốn tài nguyên. Các tùy chọn khác đang sử dụng thư viện của bên thứ ba như Netflix Ribbon hoặc proxy có thể lập trình như Envoy.

Điều gì xảy ra nếu bạn bỏ qua vấn đề cân bằng?

Bạn có thể chọn không sử dụng cân bằng tải mà vẫn không nhận thấy bất kỳ thay đổi nào. Hãy xem xét một vài tình huống làm việc.

Nếu bạn có nhiều máy khách hơn máy chủ thì đây không phải là vấn đề lớn.

Giả sử có năm máy khách kết nối với hai máy chủ. Ngay cả khi không có sự cân bằng, cả hai máy chủ sẽ được sử dụng:

Cân bằng tải và mở rộng quy mô các kết nối tồn tại lâu dài trong Kubernetes

Các kết nối có thể không được phân bổ đồng đều: có thể bốn máy khách được kết nối với cùng một máy chủ, nhưng rất có thể cả hai máy chủ sẽ được sử dụng.

Điều rắc rối hơn là kịch bản ngược lại.

Nếu bạn có ít máy khách hơn và nhiều máy chủ hơn, tài nguyên của bạn có thể không được sử dụng đúng mức và nguy cơ tắc nghẽn sẽ xuất hiện.

Giả sử có hai máy khách và năm máy chủ. Trong trường hợp tốt nhất, sẽ có hai kết nối cố định đến hai trong số năm máy chủ.

Các máy chủ còn lại sẽ không hoạt động:

Cân bằng tải và mở rộng quy mô các kết nối tồn tại lâu dài trong Kubernetes

Nếu hai máy chủ này không thể xử lý các yêu cầu của máy khách, việc chia tỷ lệ theo chiều ngang sẽ không giúp ích được gì.

Kết luận

Các dịch vụ Kubernetes được thiết kế để hoạt động trong hầu hết các kịch bản ứng dụng web tiêu chuẩn.

Tuy nhiên, khi bạn bắt đầu làm việc với các giao thức ứng dụng sử dụng kết nối TCP liên tục, chẳng hạn như cơ sở dữ liệu, gRPC hoặc WebSockets, các dịch vụ sẽ không còn phù hợp nữa. Kubernetes không cung cấp cơ chế nội bộ để cân bằng các kết nối TCP liên tục.

Điều này có nghĩa là bạn phải viết các ứng dụng có lưu ý đến sự cân bằng phía máy khách.

Bản dịch do nhóm chuẩn bị Kubernetes aaS từ Mail.ru.

Những gì khác để đọc về chủ đề này:

  1. Ba cấp độ tự động điều chỉnh trong Kubernetes và cách sử dụng chúng hiệu quả
  2. Kubernetes theo tinh thần vi phạm bản quyền với một mẫu để triển khai.
  3. Kênh Telegram của chúng tôi về chuyển đổi kỹ thuật số.

Nguồn: www.habr.com

Thêm một lời nhận xét