3 điểm bởi GN⁺ 2024-09-22 | 1 bình luận | Chia sẻ qua WhatsApp
  • 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
  • Để 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:3000 với tên dịch vụ service1

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 deploy sẽ 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 deploy chỉ 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 GET tới /up vớ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ùng app1.example.com, sẽ trả về lỗi khi service2 được triển khai với cùng host đó
    • Sau khi gỡ service1, có thể triển khai service2 lê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 /api tới web-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/123 sẽ được chuyển tới web-1 dướ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ờ --tls khi triển khai
    • kamal-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 run bằng biến môi trường
  • kamal-proxy run sẽ đọc giá trị của từng tùy chọn từ biến môi trường nếu được thiết lập
    • kamal-proxy run --http-port 8080
    • HTTP_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ệt
    • KAMAL_PROXY_HTTP_PORT=8080 kamal-proxy run
  • Build cục bộ được thực hiện bằng make trong 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

 
GN⁺ 2024-09-22
Ý 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ọn deploy

    • Có thể hiểu “đã deploy” ở đây là đang chạy và đã được đăng ký với proxy
    • Nếu lưu lượng vào đi từ proxy, thì tôi không rõ deploy còn có thể mang nghĩa gì khác ngoài “lưu lượng từ proxy đã bắt đầu chảy sang instance app mới”
  • Dạ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 curl
    Việc xây cả một hệ thống đăng ký có vẻ hơi quá tay

    • Đây chỉ là một phần rất nhỏ của Kamal(https://kamal-deploy.org)
      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ó thể giải thích thêm về cách làm này thì tốt quá
      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

    • Kamal có cảm giác được xây dựng trên tiền đề “Kubernetes quá phức tạp”, rồi từ lập luận đó lại tái tạo ra khá nhiều thứ mà Kubernetes vốn đã làm
      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
    • Có vẻ đang trộn lẫn orchestration với proxy
      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
    • Tôi không biết bạn đọc ở đâu rằng Kamal dùng Docker Swarm
      Nhìn qua thì có vẻ nó không dùng Swarm
    • Kamal dùng lệnh docker run thông thường, và không có Swarm hay orchestration
      Về cơ bản nó là một công cụ đơn giản để thay thế Capistrano
    • Nếu đó là lý do thì có cảm giác bạn còn chưa tìm hiểu ở mức rất cơ bản về chủ đề này
      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

    • Bước đầu tiên là tách migration khỏi deploy
      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”
    • Migration phải tương thích ngược, để schema cơ sở dữ liệu có thể hỗ trợ cả hai phiên bản của ứng dụng
      Đâ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
    • Khi thực sự cần triển khai không downtime thì các câu trả lời khác đã giải thích cách làm, nhưng với phần lớn tổ chức và phần lớn migration, thời gian gián đoạn do migration DB trên thực tế gần như không phân biệt được với 0
      Đ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
    • Tôi chưa trực tiếp dùng, nhưng có vẻ Xata đã tạo ra một cách giải quyết khá gọn cho migration DB, ít nhất là với Postgres
      Hai phiên bản ứng dụng có thể chạy đồng thời
      https://xata.io/blog/multi-version-schema-migrations
    • Đúng vậy, ở một thời điểm nào đó cả hai phiên bản đều phải cùng chạy
      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

    • Nó dùng gói autocert, mà gói này gần như là một triển khai tối thiểu
      Nó 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_durationtry_interval trong reverse_proxy
      Cá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 IP
      May 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ụ web
    Nế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 web mà 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ới
    Nhưng nếu chạy docker compose up --build --force-recreate web thì các bản sao hiện có sẽ bị hạ xuống, khiến mọi thứ trở nên vô nghĩa

    • Với câu hỏi đầu tiên, các bản sao còn lại được Docker cân bằng tải bằng VIP hoặc bằng cách trả về nhiều IP trong truy vấn DNS[0]
      Tô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 deploy
      Có 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...
    • Tôi không hiểu vì sao lại không dùng k8s rollout restart deployment
      Hoặ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

    • Tôi tò mò không biết có ai từng làm vậy trong môi trường production thật chưa
      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
    • Thành thật mà nói, nghe giống mẫu sống nguy hiểm
    • Caddy có làm việc này
      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

    • Thực ra trước bản pre-release “v2.0.0” này thì họ dùng Traefik
      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
    • Tôi đã thử tóm tắt một phần lý do ở đây https://nts.strzibny.name/kamal-proxy/
      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