Kamal Proxy, proxy HTTP tối giản cho triển khai zero-downtime
(github.com/basecamp)- Kamal Proxy là một proxy HTTP nhỏ chạy phía trước ứng dụng web, được thiết kế để chuyển triển khai sang instance mới mà không làm gián đoạn lưu lượng đang diễn ra
- Instance mới được đăng ký bằng
kamal-proxy deploy, và proxy chỉ định tuyến lưu lượng mới tới instance đó sau khi vượt qua kiểm tra sức khỏe HTTP - Lệnh triển khai sẽ chờ cho đến khi toàn bộ lưu lượng rời khỏi instance cũ, nên sau khi trả về thành công có thể loại bỏ instance cũ mà không làm gián đoạn các request đang xử lý
- Cung cấp định tuyến theo host, định tuyến theo đường dẫn, TLS tự động và khả năng chỉ định chứng chỉ TLS thủ công để vận hành nhiều ứng dụng trên một proxy duy nhất
- Kamal Proxy được thiết kế như một phần của công cụ triển khai Kamal, nhưng cũng có thể chạy độc lập hoặc dùng như một thành phần trong công cụ triển khai khác
Kamal Proxy làm gì
- Kamal Proxy là một proxy HTTP nhỏ giúp việc điều phối triển khai zero-downtime trở nên dễ dàng
- Khi chạy ứng dụng web phía sau Kamal Proxy, có thể triển khai thay đổi mà không làm gián đoạn lưu lượng đang diễn ra
- Không cần sự phối hợp đặc biệt nào từ phía ứng dụng để đạt được điều này
- Kamal Proxy được thiết kế để hoạt động như một phần của Kamal, bao gồm đóng gói container và provisioning
- Cũng có thể dùng độc lập hoặc như một phần của công cụ triển khai khác
Cách chạy và luồng triển khai
- Chạy instance proxy bằng lệnh
kamal-proxy run - Không có tệp cấu hình; có thể chỉ định tùy chọn khi giá trị mặc định không phù hợp với ứng dụng
- Cổng HTTP mặc định là
80 - Để chạy trên cổng khác, dùng
kamal-proxy run --http-port 8080 - Có thể xem toàn bộ tùy chọn bằng
kamal-proxy help run
- Cổng HTTP mặc định là
- Để gửi lưu lượng tới ứng dụng web, hãy deploy instance ứng dụng lên proxy
- Instance đích được chỉ định theo định dạng
hostname:port- Ví dụ:
kamal-proxy deploy service1 --target web-1:3000 - Lệnh này đăng ký
web-1:3000với tên dịch vụservice1
- Ví dụ:
Cách chuyển đổi zero-downtime
- Khi instance mới được đăng ký, Kamal Proxy ngay lập tức thực hiện kiểm tra sức khỏe HTTP để xác minh khả năng truy cập và trạng thái hoạt động
- Nếu kiểm tra sức khỏe thành công, proxy bắt đầu định tuyến lưu lượng mới tới instance mới
- Nếu instance mới không đạt trạng thái healthy trong thời gian phù hợp, lệnh
deploysẽ hủy triển khai và trả về mã thoát khác 0 - Mỗi lần triển khai sẽ chuyển toàn bộ lưu lượng từ instance đã triển khai trước đó sang instance mới
- Lệnh
deploychỉ trả về sau khi toàn bộ lưu lượng đã rời khỏi instance cũ- Sau khi trả về thành công, có thể loại bỏ instance cũ mà không làm gián đoạn các request đang xử lý
- Vì instance mới chỉ nhận lưu lượng khi ở trạng thái healthy và instance cũ chỉ bị loại bỏ sau khi đã drain hoàn toàn, quá trình triển khai diễn ra zero-downtime
Cấu hình kiểm tra sức khỏe
- Kiểm tra sức khỏe mặc định gửi request
GETtới/upvới tần suất 1 lần mỗi giây cho từng dịch vụ - Phản hồi
200được coi là trạng thái healthy - Để thay đổi kiểm tra sức khỏe cho phù hợp với ứng dụng, hãy dùng các cờ của
deploy--health-check-path--health-check-port--health-check-timeout--health-check-interval
- Ví dụ thay đổi đường dẫn kiểm tra sức khỏe:
kamal-proxy deploy service1 --target web-1:3000 --health-check-path web/index.html
- Nếu endpoint health được expose trên cổng khác với dịch vụ chính:
kamal-proxy deploy service1 --target web-1:3000 --health-check-port 8080
Tính năng định tuyến
-
Định tuyến theo host
- Định tuyến theo host cho phép dùng một instance Kamal Proxy để định tuyến lưu lượng tới nhiều ứng dụng trên cùng một máy chủ
- Có thể chỉ định host cụ thể khi triển khai
kamal-proxy deploy service1 --target web-1:3000 --host app1.example.com- Instance được triển khai theo cách này chỉ nhận lưu lượng của host đã chỉ định
- Nếu chỉ định host riêng cho từng ứng dụng, có thể chạy nhiều ứng dụng trên cùng một máy chủ mà không bị xung đột cổng
- Một host cụ thể tại một thời điểm chỉ có thể được định tuyến tới một dịch vụ
- Nếu
service1đã dùngapp1.example.com, sẽ trả về lỗi khiservice2được triển khai với cùng host đó - Sau khi gỡ
service1, có thể triển khaiservice2lên cùng host đó
-
Định tuyến theo đường dẫn
- Định tuyến theo đường dẫn dùng cho các ứng dụng chia lưu lượng tới các dịch vụ khác nhau dựa trên đường dẫn request
- Các dịch vụ có thể được mount dưới các tiền tố đường dẫn khác nhau
- Ví dụ gửi các request bắt đầu bằng
/apitớiweb-1:kamal-proxy deploy service1 --target web-1:3000 --path-prefix=/api - Ví dụ gửi phần còn lại tới
web-2:kamal-proxy deploy service2 --target web-2:3000 - Theo mặc định, tiền tố đường dẫn sẽ bị loại bỏ trước khi chuyển tới upstream
- Request
/api/users/123sẽ được chuyển tớiweb-1dưới dạng/users/123 - Để chuyển nguyên đường dẫn gốc, chỉ định
--strip-path-prefix=false
Hỗ trợ TLS
- Kamal Proxy có thể tự động lấy và gia hạn chứng chỉ TLS cho ứng dụng
- Bật TLS tự động bằng cách thêm cờ
--tlskhi triển khaikamal-proxy deploy service1 --target web-1:3000 --host app1.example.com --tls
- TLS tự động yêu cầu phải chỉ định host
- Đây là điều kiện để ngăn việc yêu cầu chứng chỉ một cách ác ý cho các hostname tùy ý
- Trong định tuyến theo đường dẫn, tùy chọn TLS phải được đặt ở đường dẫn gốc
- Các dịch vụ được triển khai ở đường dẫn khác trên cùng host sẽ dùng cấu hình TLS được chỉ định ở đường dẫn gốc
- Nếu cần cài đặt chứng chỉ TLS lấy thủ công, chứng chỉ từ CA riêng hoặc Cloudflare origin certificate, có thể chỉ định trực tiếp đường dẫn tệp chứng chỉ và khóa riêng
--tls-certificate-path cert.pem--tls-private-key-path key.pem
Biến môi trường và build
- Trong các môi trường như chạy container Docker, có thể chỉ định các tùy chọn của
runbằng biến môi trường kamal-proxy runsẽ đọc giá trị của từng tùy chọn từ biến môi trường nếu được thiết lậpkamal-proxy run --http-port 8080HTTP_PORT=8080 kamal-proxy run
- Nếu tên biến môi trường xung đột với môi trường hiện có, có thể thêm tiền tố
KAMAL_PROXY_để phân biệtKAMAL_PROXY_HTTP_PORT=8080 kamal-proxy run
- Build cục bộ được thực hiện bằng
maketrong môi trường Go đang hoạt động - Để build bằng container Docker, dùng
make docker - Cấu hình Docker Compose để thử các lệnh proxy có trong thư mục
example
1 bình luận
Ý kiến trên Hacker News
Việc chọn deploy làm tên thao tác nghe khá kỳ
Đây vốn đã là một thuật ngữ bị dùng quá tải, nên dễ gây ra kiểu nhầm lẫn như “app đã được deploy chưa? hay là deploy vào proxy rồi?”
Đáng ra có thể dùng các từ như
bind,intercept, hay đơn giản làproxy, nên không rõ vì sao lại chọndeployDạo này cũng có thể làm triển khai không downtime khá dễ với các server khác, nên việc biến thứ này thành hẳn một ứng dụng riêng khá thú vị
Ví dụ, với app + web proxy hỗ trợ Unix socket, chỉ cần di chuyển file là có thể làm không downtime
Cách này có tính nguyên tử, và cũng có thể gửi request làm nóng bằng
curlViệc xây cả một hệ thống đăng ký có vẻ hơi quá tay
Kamal là công cụ triển khai được dùng để chuyển từ cloud sang phần cứng tự vận hành và tiết kiệm hàng triệu USD(https://basecamp.com/cloud-exit)
Nếu có bài viết nào để tham khảo, tôi muốn tìm hiểu thêm
Có vẻ thứ này chủ yếu nhằm xử lý vấn đề gốc là lưu lượng bị ngắt khi thay container dịch vụ trong Docker Swarm
Tôi từng gặp vấn đề tương tự khi dựng server JAMStack ở Cloud 66, và đã dùng Caddy thay vì proxy tự viết; Traefik có lẽ cũng hoàn toàn phù hợp
Tôi không rõ vì sao Kamal chọn Swarm thay vì k8s hay k3s, nhưng độ phức tạp kiểu gì cũng phải nằm ở đâu đó, không thể biến mất, nên có vẻ cuối cùng họ đi theo hướng tự làm proxy
Tôi chưa trực tiếp dùng nó, nhưng khá hoài nghi vì có vẻ sẽ phải liên tục đuổi theo hỗ trợ WebSockets, SSE, HTTP/3, đủ kiểu nén và mã hóa
Nó giống như một ví dụ về việc một reverse proxy khởi đầu đơn giản rồi dần hấp thụ chính độ phức tạp mà nó muốn tránh
Tôi nghĩ một hệ thống runtime rộng và có khả năng mở rộng thì vẫn cần đối thủ cạnh tranh, nhưng Kamal trông như một nỗ lực lùi dần về chính sự phức tạp mà nó từng né tránh
Điều đó lại càng làm nổi bật năng lực và độ linh hoạt của Kubernetes như một framework quản lý trạng thái mong muốn
Bạn vẫn có thể tiếp tục dùng Docker, nhưng dùng Caddy, Traefik hoặc Envoy làm proxy
Ngay cả Kubernetes rốt cuộc cũng thường dùng chúng làm ingress controller
Nhìn qua thì có vẻ nó không dùng Swarm
docker runthông thường, và không có Swarm hay orchestrationVề cơ bản nó là một công cụ đơn giản để thay thế Capistrano
Nền tảng của các dịch vụ điều phối container là dùng ingress controller để xử lý kết nối và triển khai blue/green
Nói chính xác hơn, tên vai trò được gán cho rất nhiều reverse proxy vốn đã xử lý use case này từ nhiều năm, thậm chí hàng chục năm trước, chính là ingress controller
Sẽ rất hay nếu ai đó có thể giải thích ngắn gọn triển khai không downtime thường hoạt động như thế nào
Tôi đoán là hai phiên bản của ứng dụng cùng chạy song song, rồi lưu lượng mới được định tuyến sang phiên bản mới
Nhưng nếu dùng chung một cơ sở dữ liệu, và phiên bản app mới có thay đổi schema, thì script migration khi khởi động sẽ sửa schema, trong khi phiên bản app cũ vẫn mong đợi schema cũ
Tôi muốn biết người ta xử lý chuyện đó như thế nào
Nhiều framework chạy migration khi deploy code, nhưng trên thực tế tốt hơn là tự kiểm soát thời điểm chạy
Mỗi phiên bản code cần chạy được với cả schema hiện tại lẫn schema sau migration trong tương lai, nên về bản chất đó là code tương thích ngược
Quy trình sẽ là “deploy code mới chạy được với cả schema hiện tại/tương lai → xác nhận → chạy migration → xác nhận → dọn phần code xử lý schema đã biến mất”
Đây là chi phí bổ sung phải chấp nhận nếu muốn triển khai không downtime hay rolling deploy, và các công ty lớn thường làm như vậy
Điều này càng đúng nếu bạn phục vụ người dùng theo từng khu vực và có thể chọn khung giờ vắng để deploy
Hai phiên bản ứng dụng có thể chạy đồng thời
https://xata.io/blog/multi-version-schema-migrations
Load balancer bắt đầu nhận kết nối ở Server2, còn Server1 thì không nhận kết nối mới nữa
Sau đó khi mọi kết nối trên Server1 đã đóng hết thì ngắt Server1
Có thể là hai server khác nhau, hoặc nhiều worker trên cùng một server
Trong khoảng thời gian đó, migration phải tương thích ngược như các câu trả lời khác đã nói
Kamal 2 hiện đang ở bản RC(https://github.com/basecamp/kamal/releases) và có vẻ sẽ hỗ trợ SSL tự động, nên khá đáng mong đợi vì sẽ giúp chạy nhiều ứng dụng trên một máy chủ bằng Kamal dễ dàng hơn
autocert, mà gói này gần như là một triển khai tối thiểuNó khá mong manh, và vì giới hạn cấp phát của Let's Encrypt cùng giới hạn số chứng chỉ đồng thời nên rất khó mở rộng ngang các instance proxy
Caddy/Certmagic giải quyết vấn đề này bằng cách ghi dữ liệu vào kho lưu trữ dùng chung để chỉ cấp phát một chứng chỉ, rồi mọi instance tái sử dụng và điều phối qua kho đó
Cũng không có thay thế nhà phát hành, né giới hạn cấp phát hay hỗ trợ ARI
Việc giữ request lại cho tới khi upstream sẵn sàng thì Caddy xử lý tốt nếu đặt
try_durationvàtry_intervaltrongreverse_proxyCách xử lý header của proxy cũng không xét đến IP đáng tin cậy, nên nếu bật lên thì ai đó có thể đặt
X-Forwarded-Forđể giả mạo IPMay là mặc định đang tắt, nhưng cũng không có cảnh báo nào
Tôi hiểu mục tiêu là sự đơn giản, nhưng ở trạng thái hiện tại thì nó còn non và có quá nhiều lựa chọn tốt hơn nên tôi không muốn dùng trong thực tế
Tôi không rõ phải dùng cái này như thế nào và có vẻ như mình đang bỏ sót điều gì đó
Ví dụ khởi động 4 bản sao của dịch vụ
webNếu có thể deploy vào một bản sao như
example-web-1để tạo dịch vụ, thì tôi không hiểu 3 bản sao còn lại làm gìĐể cập nhật
webmà vẫn zero-downtime, có vẻ phải chạy lệnh build trên dịch vụweb, rồi khởi động dịch vụ này bằng cách nào đó, sau đó chạy deploy cho target mớiNhưng nếu chạy
docker compose up --build --force-recreate webthì các bản sao hiện có sẽ bị hạ xuống, khiến mọi thứ trở nên vô nghĩaTôi chưa kiểm tra proxy này có cân bằng tải trên nhiều bản ghi trả về từ truy vấn DNS hay không, nhưng ít nhất nếu là cân bằng tải dựa trên VIP thì nó sẽ hoạt động như kỳ vọng
Phần cập nhật thứ hai thì kém rõ ràng hơn
Có thể kỳ vọng là dựng một dịch vụ khác tên trong cùng mạng rồi chạy
kamal-proxy deployCó thể là kiểu đưa số phiên bản vào tên dịch vụ, và nếu muốn rollback nhanh thì giữ phiên bản trước ở trạng thái nóng cũng hợp lý
[0]: https://docs.docker.com/reference/compose-file/deploy/#endpo...
k8s rollout restart deploymentHoặc cũng có thể chuyển DNS hay router giữa hai backend
Tôi tò mò liệu đây có phải là triển khai mẫu tạm dừng lưu lượng hay không
Theo cách proxy về cơ bản dừng lưu lượng trong vài giây, nên các request đi vào chỉ mất thêm vài giây so với bình thường nhưng vẫn hoàn tất sau một khoảng trễ ngắn
Trong vài giây đó, bạn có thể chạy một migration DB nhỏ hoặc một thay đổi hạ tầng dạng chặn phức tạp hơn một chút nhưng hoàn thành trong dưới 5 giây
Nghe khá rủi ro, và dù tôi đã làm ở công ty máy chủ ứng dụng vài năm thì đây là lần đầu tôi nghe tới mẫu này
Tuy vậy, vì ông ấy là đồng sáng lập Django nên hẳn đã chứng kiến nhiều đợt deploy, nên cũng khó mà gạt đi
Tôi nhớ trước đây từng thảo luận về chủ đề này
Tôi muốn biết đã có giải thích ở đâu về lý do quyết định làm proxy tự viết thay vì dùng Traefik hay công cụ đã được kiểm chứng khác chưa
Trong PR có bối cảnh về lý do họ đổi và quyết định tự triển khai
https://github.com/basecamp/kamal/pull/940
Nhưng có vẻ họ chưa từng chính thức công bố phần so sánh với những lựa chọn khác ngoài Traefik
Tôi muốn biết có cách nào đặt timeout không
https://github.com/basecamp/kamal-proxy/blob/main/internal/s...
https://github.com/basecamp/kamal-proxy/blob/main/internal/s...
https://blog.cloudflare.com/exposing-go-on-the-internet/
Đúng kiểu NIH. Không còn gì để nói thêm