Mở rộng quy mô OpenComputer: Từ 1 máy ảo đến 1 triệu sandbox
OpenComputer đã giải quyết bài toán mở rộng hệ thống từ một máy ảo đơn lẻ bị giới hạn hạn ngạch CPU lên khả năng vận hành hàng triệu sandbox bằng kiến trúc đa đám mây. Bài viết này sẽ đi sâu vào cách họ tái cấu trúc hệ thống thành các "cell" độc lập và sử dụng lớp Edge để định tuyến thông minh, giúp việc mở rộng dung lượng trở nên dễ dàng như một bước triển khai thông thường.

Mở rộng quy mô OpenComputer: Từ 1 máy ảo đến 1 triệu sandbox
Chúng tôi bắt đầu OpenComputer chỉ với một máy ảo (VM) duy nhất tại một khu vực của Azure. Công ty phát triển nhanh chóng, nhưng Azure không thể tăng hạn ngạch tính toán (compute quota) cho chúng tôi tại khu vực đó thêm nữa. Chúng tôi rơi vào tình trạng phải cố gắng tăng trưởng dựa trên một kho tài nguyên CPU cố định.
Vì vậy, chúng tôi buộc phải tìm một cách khác để mở rộng quy mô. Bài viết này sẽ đề cập đến những gì chúng tôi đã làm để đạt được khả năng thêm dung lượng gần như vô hạn. Chúng tôi sẽ đi qua cách chúng tôi chia nhỏ hệ thống thành các "cell" (ô), cách một sổ đăng ký toàn cầu tại lớp Edge quyết định vị trí của mỗi sandbox, và cách bốn nhà cung cấp đám mây cộng lại giúp chúng tôi đạt được hàng triệu CPU.
Kiến trúc Cell và trách nhiệm của nó
Toàn bộ dung lượng của chúng tôi từng nằm trong hạn ngạch của một khu vực Azure
Sandbox thực chất là các máy ảo đầy đủ, máy ảo cần phần cứng vật lý, và các nhà cung cấp đám mây phân bổ phần cứng đó theo từng khu vực. Vấn đề của chúng tôi bắt đầu từ việc phân bổ này, vì vậy việc làm rõ cách nó hoạt động sẽ rất hữu ích.
Đằng sau hạn ngạch đám mây
Mọi nhà cung cấp đám mây đều giới hạn bạn dựa trên dung lượng khu vực, về cơ bản là nơi trung tâm dữ liệu vật lý tồn tại với phần cứng hữu hạn và một hàng dài khách hàng muốn sử dụng nó.
Do đó, các nhà cung cấp cấp phát dung lượng dưới dạng hạn ngạch, được đo bằng tổng số CPU. Hạn ngạch bắt đầu nhỏ và chỉ tăng sau khi bạn xây dựng được lịch sử sử dụng. Nguyên tắc hoạt động là chạy mức phân bổ hiện tại ở mức khoảng 50% khả năng trong một hoặc hai tuần, sau đó yêu cầu tăng thêm, và để dữ liệu sử dụng biện minh cho yêu cầu đó. Yêu cầu 10.000 CPU vào ngày đầu tiên chắc chắn sẽ nhận được câu trả lời là "Không".
Chúng tôi sớm chạm trần 300 CPU
Khu vực đầu tiên của chúng tôi là Azure US East 2, với hạn ngạch ban đầu khoảng 300 CPU. Kế hoạch là sử dụng hết số này và yêu cầu thêm, giống như quy trình thông thường.
Tuy nhiên, không may chúng tôi đã chọn một trong những trung tâm dữ liệu bận rộn nhất thế giới, và 300 CPU là trần giới hạn ở đó bất kể lịch sử sử dụng của chúng tôi. Trong khi đó, người dùng mới vẫn tiếp tục đăng ký sử dụng một kho tính toán cố định.
Tại sao việc di chuyển sang khu vực khác sẽ không giải quyết vấn đề quy mô
Bước đi hiển nhiên là chuyển đến một khu vực yên tĩnh hơn hoặc một đám mây khác để thu thập hạn ngạch lớn hơn ở đó. Chúng tôi cũng có các tín dụng Azure muốn sử dụng. Nhưng vấn đề sâu xa hơn là việc di chuyển khu vực hoặc nhà cung cấp đám mây chỉ đơn giản là đặt lại đồng hồ đếm ngược.
Mọi khu vực đều có giới hạn trên, nhà cung cấp sẽ tăng dần nó, nhưng một kiến trúc đơn khu vực phát triển đủ nhanh sẽ sớm chạm trần tại một thời điểm nào đó.
Hơn nữa, nhu cầu sandbox ở mức 10.000, 100.000 hoặc 1 triệu máy ảo đồng thời không thể được phục vụ về mặt vật lý từ một trung tâm dữ liệu duy nhất. Chúng tôi cần suy nghĩ lại kiến trúc để việc thêm một khu vực của bất kỳ đám mây nào trở thành một bước triển khai (deployment step) thay vì một dự án di chuyển (migration project). Và chúng tôi phải tháo dỡ kiến trúc hiện có để làm được điều đó.
Bắt đầu với một mặt phẳng điều khiển duy nhất
Phiên bản đầu tiên của OpenComputer là một máy ảo duy nhất xử lý mọi thứ với một mặt phẳng điều khiển (control plane).
Phiên bản thứ hai là nơi chúng tôi mở rộng quy mô lên nhiều máy ảo trong một khu vực, được điều phối bởi một mặt phẳng điều khiển duy nhất làm mọi thứ: giao diện web, bảng điều khiển, logic thanh toán, và việc điều phối máy ảo thực tế, tất cả ở một nơi.
Thiết kế đó phục vụ khoảng một nghìn sandbox một cách thoải mái. Nó giả định rằng mọi tài nguyên tính toán đều sống trong một khu vực, và thành phần điều phối máy ảo cũng có thể là thành phần chạy sản phẩm xung quanh chúng. Chúng tôi phải tách hai công việc này ra.
Cells biến dung lượng thành một đơn vị có thể triển khai
Chúng tôi bắt đầu bằng việc cắt giảm mặt phẳng điều khiển xuống còn một công việc duy nhất (điều phối máy ảo), và đóng gói nó cùng với các worker mà nó quản lý thành một đơn vị triển khai vào bất kỳ khu vực đám mây nào. Đơn vị đó được gọi là "cell".
Mặt phẳng điều khiển thu nhỏ lại thành một công việc
Việc thiết kế lại đã lột bỏ mặt phẳng điều khiển để chỉ còn lại vòng đời của các máy ảo trong khu vực của chính nó:
- Lên lịch cho một sandbox được yêu cầu lên một worker cụ thể.
- Theo dõi nơi mọi máy ảo sống và trạng thái của nó.
- Ngủ đông (hibernate) các máy ảo nhàn rỗi vào bộ lưu trữ S3 và đánh thức chúng khi có yêu cầu.
- Di chuyển máy ảo từ worker này sang worker khác khi cần thiết để cân bằng lại.
Các thành phần bên trong một cell chỉ biết về nhau và không biết gì về bên ngoài cell. Bảng điều khiển, logic thanh toán và mọi thứ hướng tới người dùng đã được chuyển hoàn toàn ra khỏi cell.
Một cell là đơn vị nhỏ nhất để cô lập lỗi, và định danh của nó mã hóa mọi thứ về khu vực máy chủ mà nó sống. Mỗi cell sở hữu từ 5 đến 10 worker chạy máy ảo QEMU.
Đặt máy ảo phức tạp hơn việc chọn worker ít tải nhất
Khi một yêu cầu tạo được gửi đi với thông tin về bộ nhớ, CPU, đĩa và mẫu (template), mặt phẳng điều khiển có thể chọn một worker cụ thể dựa trên một số tiêu chí nhất định. Một mặt phẳng điều khiển đơn giản sẽ tìm worker ít tải nhất và tạo máy ảo ở đó.
Một mặt phẳng điều khiển tốt cần cân nhắc:
- Sự phù hợp về tài nguyên để phân mảnh không giết chết dung lượng.
- Độ ấm của mẫu (một worker đang giữ snapshot vàng ấm sẽ tạo sandbox trong khoảng 200ms, trong khi worker lạnh mất vài giây).
- Sự khác biệt về phần cứng như ARM so với amd64.
- Mối liên kết mềm (soft affinity) giữ các sandbox của một tổ chức gần bộ nhớ đệm của chúng.
- Sự liên kết ngược (anti-affinity) giữ khối lượng công việc có phí tránh xa các hàng xóm miễn phí ồn ào.
Cùng một cell triển khai vào bất kỳ đám mây nào
Một cell không đưa ra bất kỳ giả định nào về nhà cung cấp mà nó chạy trên đó. Hình dạng worker mà chúng tôi sử dụng có loại phiên bản tương đương trên AWS, Azure, GCP và OCI, do đó cùng một cell có thể triển khai vào bất kỳ nhà cung cấp nào trong số đó mà không cần thay đổi.
Với điều này, dung lượng không còn là "hạn ngạch Azure của chúng tôi" mà trở thành "tổng số của mọi cell mà chúng tôi đã triển khai".
Một sổ đăng ký toàn cầu tại lớp Edge định tuyến mọi sandbox
Với nhiều cell độc lập, cần có một thứ gì đó để trả lời câu hỏi cell nào sẽ nhận sandbox. Công việc đó nằm ngoài mọi cell, tại lớp Edge.
Cách một yêu cầu tạo tìm thấy cell của nó
Lớp Edge là một tập hợp các Cloudflare Workers, với cơ sở dữ liệu D1 giữ sổ đăng ký toàn cầu và Durable Objects nhận luồng sự kiện từ các cell.
Sổ đăng ký đó biết mọi cell đang tồn tại, nơi nó chạy và bao nhiêu dung lượng trống nó nắm giữ. Chúng tôi coi nó như một mặt phẳng điều khiển của các mặt phẳng điều khiển.
Một yêu cầu tạo sẽ hạ cánh tại vị trí Edge gần nhất, Worker sẽ chọn cell trống nhất, và từ đó mặt phẳng điều khiển của cell đó sở hữu sandbox.
Chỉ có thao tác tạo mới tốn thời gian tại Edge
Việc tạo là hiếm và tốn kém anyway, vì vậy việc dành thêm 50 đến 100ms tại Edge để xác thực, kiểm tra tín dụng và chọn cell là một sự đánh đổi hợp lý.
Mọi thao tác thường xuyên (exec, đọc/ghi tệp, lưu lượng PTY, destroy) đều đi thẳng đến mặt phẳng điều khiển của cell qua một kết nối được xác thực bởi JWT đã ký, với không có tra cứu đồng bộ Cloudflare nào trong đường dẫn.
Lớp Edge quyết định nơi một sandbox được sinh ra, và sau đó cell sẽ phục vụ nó trực tiếp.
Tại sao Cloudflare Workers và cơ sở dữ liệu D1 chịu trách nhiệm lớp này
Khối lượng công việc của sổ đăng ký nhỏ và thiên về đọc: một hàng cho mỗi cell, bộ đếm dung lượng, trạng thái vòng đời.
Cơ sở dữ liệu D1 là đủ cho việc đó, và việc chạy tra cứu tại Edge có nghĩa là quyết định định tuyến diễn ra gần người dùng mà không cần chuyến vòng khứ hồi đến khu vực chính.
Phần còn lại của trạng thái toàn cầu sống trong cùng một ngăn xếp. KV giữ các phiên và khóa loại bỏ sự kiện trùng lặp, R2 lưu trữ luồng sự kiện thô, và Durable Objects giữ kế toán tín dụng theo tổ chức và trạng thái định tuyến theo sandbox.
Các nhịp tim giữ sổ đăng ký và thanh toán được cập nhật
Định tuyến là một nửa hạ lưu của lớp Edge. Một nửa thượng nguồn là luồng sự kiện chảy từ mọi máy ảo đang chạy quay lại qua Durable Objects và vào sổ đăng ký.
Thanh toán theo giây từ nhịp tim 10 giây
Sự kiện quan trọng nhất trong luồng đó là nhịp tim (heartbeat).
Mọi máy ảo đang chạy đều báo cáo "vẫn còn sống" mỗi 10 giây, và chúng tôi tổng hợp các tích tắc đó vào thanh toán, đó là cách chúng tôi tính phí cho mỗi giây sandbox thực sự chạy.
Các thay đổi trạng thái được đẩy đến sổ đăng ký thay vì chờ thăm dò
Cùng một luồng đó cũng mang theo các sự kiện vòng đời. Ví dụ, một máy ảo ngủ đông, bị dừng hoặc di chuyển, và sổ đăng ký được cập nhật bằng cách đẩy (push) để quyết định định tuyến tiếp theo hoạt động dựa trên dung lượng hiện tại.
Bên trong một cell, các worker xuất bản sự kiện lên Redis Streams, và một bộ chuyển tiếp (forwarder) đóng gói chúng qua HTTPS đến một Cloudflare ingest Worker, xác thực mỗi lô bằng HMAC, loại bỏ trùng lặp trên ID sự kiện trong KV, và phân phối đến sổ đăng ký và các đối tượng thanh toán. Các sự kiện được thiết kế để có tính idempotent, và khi Cloudflare không thể tiếp cận, chúng được lưu trong bộ đệm Redis cho đến khi kết nối trở lại, do đó một cell không bao giờ mất các tích tắc thanh toán do sự cố mạng.
Sổ đăng ký chỉ tốt bằng các sự kiện cung cấp cho nó, và một sổ đăng ký cũ sẽ tiếp tục định tuyến các sandbox mới vào một cell đã đầy. Đó là lý do các thay đổi trạng thái được đẩy ngay lập tức khi chúng xảy ra thay vì chờ một yêu cầu thăm dò.
Độ trễ khởi động, ngủ đông và đánh thức trong môi trường sản xuất
Thiết kế cell chỉ có ý nghĩa nếu các máy ảo bên trong nó nhanh. Và rất nhiều nỗ lực kỹ thuật và tinh chỉnh của chúng tôi đã tập trung vào độ trễ vòng đời. Trình quản lý máy ảo (hypervisor) của chúng tôi là QEMU. Trong khi QEMU gốc khởi động lạnh mất khoảng 30 giây, chúng tôi đã đưa thời gian khởi động sandbox xuống dưới 1 giây ở mức p95.
Việc ngủ đông tạo ra điểm kiểm tra máy ảo trong bộ lưu trữ S3 và quá trình hoàn thành trong khoảng 6 giây trong trường hợp thành công điển hình. Đánh thức sandbox đã ngủ đông trung bình từ 1 đến 2 giây, và tốc độ phụ thuộc vào việc điểm kiểm tra vẫn còn ấm trên worker hay phải kéo lại từ S3.
Vị trí của chúng tôi hôm nay về mặt mở rộng quy mô
Ban đầu chúng tôi bị mắc kẹt ở 300 CPU vì toàn bộ hệ thống phụ thuộc vào một khu vực duy nhất của một nhà cung cấp đám mây. Bây giờ, đơn vị triển khai là một cell sở hữu vòng đời máy ảo của chính nó, và một khu vực chứa bao nhiêu cell tùy thuộc vào chúng tôi đặt vào đó.
Sổ đăng ký Edge định tuyến mọi việc tạo sandbox đến bất kỳ cell nào còn chỗ trống, và luồng nhịp tim giữ thanh toán trung thực ở độ chi tiết một giây.
Lời hứa sản phẩm vẫn không thay đổi qua tất cả những điều này. Bạn nhận được một máy ảo Linux đầy đủ, được lên lịch gần bạn, và được tính phí cho từng giây nó thực sự chạy.
Bạn có thể thử nghiệm tại opencomputer.dev → Toàn bộ nền tảng là mã nguồn mở tại github.com/diggerhq/opencomputer nếu bạn muốn đọc trình lập lịch và đường ống sự kiện.
