Mở đầu: câu chuyện về 2 cái server, port UDP và K3s
Mục lục

I. Một lời nhờ và một kết nối bị chặn #
Tôi cần một server, cuối cùng có hai cái chỉ vì một kết nối UDP bị chặn. Đây là lý do tôi nối chúng thành một cụm và chọn K3s thay cho Kubernetes bản đầy đủ.
Tôi cần một server cho một web app. Cuối cùng tôi có hai cái, và phải học một công cụ mới tên K3s chỉ để cả hai có việc mà làm.
Dự án web của tôi cần lên server gấp. Thay vì thuê VPS như mọi lần, tôi hỏi một người anh trong ngành có server nào đang trống không. VPS là một server ảo thuê từ nhà cung cấp cloud. Anh ấy gửi ngay một server, không chút do dự.
Lúc cài đặt, tôi phát hiện server này bị nhà mạng chặn hẳn UDP, cả chiều vào lẫn chiều ra. Khổ nỗi kiến trúc dự án của tôi bắt buộc phải dùng đúng giao thức này.
Nói nhanh cho ai chưa quen: dữ liệu đi trên mạng theo hai kiểu phổ biến, giống hai cách một quán ăn xử lý đơn giao hàng.
- TCP giống quán gọi xác nhận đơn rồi mới giao, thiếu món thì giao bù, các món tới đủ và đúng thứ tự. Chắc chắn, nhưng chậm hơn.
- UDP giống quán nhận đơn là giao luôn, không gọi xác nhận, rơi món dọc đường thì thôi. Đổi lại là nhanh, nên hầu hết VPN hiện đại chạy trên UDP.
VPN là một mạng riêng được mã hóa chạy trên internet, giúp các máy ở xa nói chuyện với nhau như đang ngồi chung một văn phòng.
Một số nhà mạng khóa sẵn UDP cũng vì thiếu cuộc gọi xác nhận đó. Kẻ tấn công gửi yêu cầu nhỏ tới server, ghi giả địa chỉ gửi là của nạn nhân, và server trả lời thẳng về nạn nhân, có khi lớn hơn yêu cầu nhiều lần. Giống ai đó đặt hàng loạt đơn ghi địa chỉ nhà người khác: đặt thì mất vài giây, đồ ăn thì chất đống trước cửa nhà người ta. Nhân lên với nhiều server, đó là một đợt DDoS, tức là lưu lượng dồn tới mức hệ thống không phục vụ nổi. Với TCP, quán gọi xác nhận trước mà nạn nhân không hề đặt, nên trò này khó thành hơn nhiều.
Chỗ chặn nằm ở phía mạng, không nằm trong server, nên chỉnh gì trong server cũng vô ích. Anh bạn tôi tạo ticket xin nhà cung cấp mở port, rồi chúng tôi ngồi chờ. Port là số hiệu cửa trên một máy; mỗi dịch vụ dùng một port riêng.
II. Từ một server thành hai #
Dự án thì không chờ được. Tôi tặc lưỡi tự bỏ tiền mua một server mới tinh ở nhà cung cấp khác.
Sáng hôm sau, bộ phận kỹ thuật báo xong ticket: họ chỉ mở được UDP cho địa chỉ IP trong nước. UDP ra nước ngoài vẫn bị chặn hoàn toàn.
Lúc đó tôi mới biết server kia không phải máy cũ để không. Anh ấy đã âm thầm tự bỏ tiền mua mới để cho tôi thuê, không tính thêm đồng nào, chỉ để tôi kịp tiến độ. Tóm lại, tôi vừa mua một server mới để thay cho một server mới tinh mua riêng cho tôi.
Tấm lòng đó tôi không thể từ chối, nên nhận luôn. Thế là tôi có trong tay 2 cái server cho đúng một web app.
Để một máy gánh hết việc trong khi máy kia cấu hình mạnh hơn nằm không thì quá phí. Tôi nảy ra ý: “Sao không nối hai máy thành một cụm, để chúng cùng gánh tải, chia tài nguyên và dự phòng cho nhau?” Cụm là một nhóm máy được phần mềm điều khiển để chạy chung như một hệ thống.
Ý tưởng đó thành ra series này. Có điều cụm cần một phần mềm điều khiển, và lựa chọn đầu tiên của tôi nặng hơn tôi tưởng.
III. Kubernetes quá nặng, K3s vừa sức #
Tôi tìm tới Kubernetes, chuẩn phổ biến nhất của ngành cho việc này. Ngay ngày đầu cài, tôi hiểu ra “chuẩn của ngành” được may cho những cụm máy to hơn hai server của tôi rất nhiều.
Kubernetes là phần mềm điều phối app trên nhiều máy: quyết định app nào chạy ở đâu, và dựng lại app khi có máy chết. Nó nặng vì control plane, bộ não của cụm. Control plane ghi nhớ trạng thái mong muốn, ví dụ “luôn có 2 bản web app đang chạy”, rồi liên tục chỉnh hệ thống cho khớp. Nó chạy thường trực, và trên VPS cỡ vừa của tôi nó ngốn gần hết RAM lẫn CPU, chẳng còn bao nhiêu cho chính app.
Lối ra là K3s, một bản Kubernetes rút gọn. K3s bỏ các tính năng cũ ít dùng và phần tích hợp riêng cho các nhà cung cấp cloud lớn, rồi gộp phần còn lại vào một file chương trình. Phần lõi giữ nguyên, nên công cụ Kubernetes thông thường vẫn dùng được.
Với tôi, đó cũng là chuyến dời nhà từ Docker, công cụ tôi vẫn dùng để chạy app trên từng server riêng lẻ, sang Kubernetes. Bài 01 sẽ kể vì sao Docker không đủ cho một cụm. Trong khi đó, UDP ra nước ngoài vẫn bị chặn.
IV. Kiến trúc cuối cùng: mạng riêng, máy chủ tiếp sức và máy ở nhà #
Bài toán ban đầu chỉ là một server không gửi được UDP ra nước ngoài. Kiến trúc cuối cùng có cả một mạng riêng, một máy chủ tiếp sức, và cái PC ở nhà tôi:
- Tailscale làm mạng lõi. Vì UDP ra nước ngoài bị chặn, VPN giữa các máy rớt liên tục và phải lùi về TCP, chậm hơn hẳn. Nên tôi chọn Tailscale, một dịch vụ VPN nối thẳng các máy dù chúng ở đâu. Nó tự tìm đường qua router và firewall, những thứ thường chặn kết nối gọi từ ngoài vào.
- Tự dựng DERP server ngay trên cụm K3s. DERP là máy chủ tiếp sức của Tailscale: hai máy không nối thẳng được thì dữ liệu đi vòng qua nó. DERP chuyển dữ liệu qua HTTPS, giao thức trình duyệt dùng để mở web, vốn chạy trên TCP, nên vẫn thông khi UDP bị chặn. Tự dựng thì đường vòng đi qua máy của tôi thay vì máy chủ chung của Tailscale ở xa, nên độ trễ thấp hơn.
- Nối PC ở nhà vào cụm để chạy tác vụ AI. Khi mạng đã thông, tôi nối thẳng PC ở nhà vào cụm. GPU là chip xử lý đồ họa, cũng là loại chip các tác vụ AI thường cần; dùng GPU sẵn ở nhà tiết kiệm một khoản lớn so với đi thuê.
Kết quả là một cụm hybrid cloud. Hybrid cloud là cách trộn máy thuê trên cloud với phần cứng của mình, ở đây là VPS và PC ở nhà tôi.
Đây cũng là lần đầu tôi thật sự bắt tay làm Kubernetes cho một dự án cá nhân. Nên series này vừa là nhật ký kiến trúc, vừa là tài liệu thực hành K3s từ con số 0, cho những ai vô tình hay cố ý có trong tay 2 cái server.
Sắp thuê hay mượn server cho một app cần UDP, như VPN hay gọi video? Hãy hỏi nhà cung cấp trước xem UDP có bị chặn không, kể cả chiều ra nước ngoài. Bài 01 bắt đầu từ chỗ Docker không còn đủ dùng.