1 điểm bởi GN⁺ 2 giờ trước | 1 bình luận | Chia sẻ qua WhatsApp
  • Deptool là một công cụ triển khai được tạo ra để tự vận hành DNS và cấu hình web server; nó trước tiên hiển thị kế hoạch thay đổi, rồi chỉ sau khi xác nhận mới áp dụng lên các host mục tiêu
  • Công cụ render trước toàn bộ cấu hình cụm để quản lý bằng Git, đặt thư mục theo từng commit dưới /var/lib/deptool trên mỗi host, sau đó chuyển phiên bản một cách nguyên tử bằng cách đổi liên kết tượng trưng current
  • Trước khi triển khai, nó lấy khóa trên từng host, so sánh commit mà máy cục bộ đang biết với trạng thái triển khai thực tế để dừng các kế hoạch lỗi thời, và chỉ tiếp tục khi đã giữ được khóa trên mọi host bị ảnh hưởng
  • Các dịch vụ chạy dưới dạng systemd unit; khi cấu hình thay đổi thì khởi động lại, và nếu khởi động thất bại thì trả liên kết về phiên bản known-good trước đó rồi khởi động lại để thực hiện rollback tự động ở mức mili giây
  • Việc thực thi từ xa dùng mô hình agent tĩnh, chỉ dùng SSH làm lớp truyền tải, nên có thể tự động cài đặt chỉ với coreutils ngay cả trong môi trường không có Python hay trình quản lý gói như Flatcar Linux

Bối cảnh tạo ra Deptool

  • Công việc bắt đầu từ việc chuyển blog sang châu Âu để tránh mâu thuẫn giữa việc viết về chủ quyền số của châu Âu nhưng lại đăng trên dịch vụ hosting của Mỹ và hyperscaler do Mỹ kiểm soát
  • Vì DNS cũng đang phụ thuộc vào Cloudflare, nên nảy sinh nhu cầu phải tự vận hành DNS server
  • Web server hiện có chạy Nginx và Lego để gia hạn chứng chỉ trên một VM nhỏ; cấu hình Nginx được tạo bằng Nix rồi chép lên server bằng một script Python nhỏ và khởi động lại Nginx
  • Để vận hành DNS server thì cần ít nhất hai máy chủ, nhiều systemd unit hơn, thêm file cấu hình và zonefile, nên script cũ không còn đủ dùng
  • Dù chuyển sang NixOS cũng là một lựa chọn, tác giả quyết định giữ cách làm hiện tại: một OS nền tối giản và chạy dịch vụ trong chroot chỉ đọc với đúng các binary cần thiết, đồng thời tự tạo công cụ triển khai mới

Deptool được dùng như thế nào

  • Deptool trước tiên hiển thị kế hoạch thay đổi cấu hình của cụm, rồi sau khi được xác nhận mới áp dụng lên các host mục tiêu
  • Ví dụ cập nhật bản ghi DNS: sau khi chạy deptool deploy, công cụ cho thấy kế hoạch thay đổi file cấu hình nsd và khởi động lại nsd.service trên s4.ruuda.nls5.ruuda.nl
  • Nếu triển khai thất bại thì rollback tự động sẽ được áp dụng; trong ví dụ, sau khi xác nhận triển khai lên 2 host của cụm prod, toàn bộ quá trình hoàn tất thành công chỉ trong 0,99 giây
  • Phần output tách riêng host mục tiêu, ứng dụng bị thay đổi, file thay đổi và các systemd unit sẽ được khởi động lại, giúp kiểm tra chính xác việc gì sẽ diễn ra trước khi triển khai

Các điều kiện của công cụ triển khai mong muốn

  • Nhanh

    • Cập nhật cấu hình phải mất dưới 1 giây; ngay cả ping xuyên Đại Tây Dương cũng chỉ cỡ 100ms, nên về bản chất không có lý do gì phải chậm hơn nhiều
  • Có thể dự đoán

    • Công cụ phải cho thấy trước những gì nó sẽ làm rồi thực hiện đúng như vậy
    • Tác giả muốn tách riêng các bước planapply như OpenTofu
    • Theo tác giả, check mode của Ansible không đủ đáng tin vì các thay đổi dây chuyền có thể chỉ lộ ra sau khi các bước mệnh lệnh đã chạy, và nó cũng không ngăn được trạng thái host thay đổi giữa lúc kiểm tra và lúc thực thi thật
  • An toàn

    • Ngay cả khi cấu hình Nginx bị lỗi, web server cũng không nên bị down vài phút; công cụ phải tự động rollback trong vài mili giây
  • Đơn giản

    • Điều cần làm chỉ là chép file cấu hình từ laptop lên server và khởi động lại vài systemd unit
    • Không cần giải quyết mọi bài toán triển khai, cũng không cần cung cấp control flow hay khả năng chạy mã tùy ý
    • Việc xử lý template file cấu hình có thể do công cụ khác đảm nhiệm; quan điểm về template YAML được tách sang generatecông cụ tạo file riêng
  • Phải mang tính khai báo

    • Nếu xóa file hoặc ứng dụng khỏi cấu hình thì chúng cũng phải bị xóa khỏi server
    • Không nên cần thêm bước dọn dẹp tường minh, cũng không được để quên rồi sinh ra drift hay file thừa
  • Không có bước thiết lập ban đầu

    • Phải có thể quản lý server ngay sau khi vừa provisioning xong
    • Nếu cần cài thủ công agent, daemon, dependency hay quy trình đăng ký host, thì lại nảy sinh thêm bài toán phải tự động hóa chính quy trình đó

Tách riêng việc tạo cấu hình và triển khai

  • Ý tưởng cốt lõi là tách tạo cấu hình khỏi triển khai
  • Unsible do David tạo ở nơi làm việc không chạy Ansible playbook theo từng bước; thay vào đó, nó tạo tarball ở local rồi gửi sang host để đặt file vào chỗ cần thiết
  • Script triển khai đơn giản trước đây cũng xây dựng cấu hình ở bên ngoài, còn bản thân script gần như chỉ làm nhiệm vụ sao chép file
  • NixOS cũng có thể được xem là áp dụng cùng ý tưởng này cho hệ thống cục bộ; điều có thể học từ Nix là lưu các đầu ra đã tạo vào nơi cho phép nhiều phiên bản cùng tồn tại, rồi giới hạn phần mệnh lệnh của system administration vào một bước kích hoạt nhỏ chỉ gồm việc đổi vài symbolic link
  • Thiết kế này phù hợp cả với quản lý gói lẫn cấu hình hệ thống

Cách Deptool hoạt động

  • Render trước toàn bộ cấu hình cụm

    • Tất cả file cấu hình của cả cụm được tạo sẵn và lưu vào một thư mục trên đĩa
    • Cây thư mục sâu hai cấp: cấp trên cùng là thư mục theo host mục tiêu, bên dưới là thư mục theo ứng dụng
  • Đưa vào kho Git

    • Đặt thư mục cấu hình vào một kho Git giúp so sánh sự khác biệt giữa các phiên bản và xem toàn cụm đã thay đổi gì
    • diffstat cho biết những host nào và ứng dụng nào bị ảnh hưởng, đồng thời có thể xem diff chính xác của từng file cấu hình
  • Hiện thực hóa file trong thư mục cô lập trên host

    • Mọi file đều được đặt dưới /var/lib/deptool để không can thiệp vào thành phần khác
    • Vì tạo thư mục theo tên commit triển khai, nhiều phiên bản có thể cùng tồn tại trên đĩa
    • Symbolic link current trỏ tới phiên bản đang triển khai, cho phép đổi phiên bản một cách nguyên tử
    • File bị xóa sẽ không được hiện thực hóa ở phiên bản tiếp theo, nên không để lại file thừa
    • Với ứng dụng yêu cầu file ở vị trí cụ thể, có thể tạo symbolic link từ vị trí cần thiết trong filesystem tới /var/lib/deptool
    • Việc tạo/xóa symbolic link không phải là nguyên tử, nhưng chỉ cần thiết khi thêm hoặc xóa link, chứ không phải khi sửa nội dung file
    • Nếu symbolic link đó không còn xuất hiện trong phiên bản triển khai sau, diff sẽ cho biết cần xóa nó nên sẽ không để sót file
  • Ghi lại trạng thái triển khai bằng remote-tracking ref

    • Laptop của người vận hành theo dõi commit đã được triển khai trên từng host
    • Trạng thái triển khai không phải thuộc tính của toàn cụm mà là thuộc tính theo từng host
    • Nếu một thay đổi không ảnh hưởng đến host nào đó thì không cần triển khai commit mới lên host đó
    • Nhờ thông tin này, có thể tính diff của cụm khi offline, và diff đó trở thành kế hoạch triển khai có thể hiển thị trong vài mili giây
  • Lấy khóa trên các host mục tiêu trước khi triển khai

    • Công cụ kết nối qua SSH để gửi yêu cầu lấy khóa; yêu cầu này bao gồm commit mà máy cục bộ tin là đã được triển khai trên host đó
    • Nếu lấy được khóa thì kế hoạch là hợp lệ, và cho tới khi nhả khóa, không có đợt triển khai nào khác có thể chạy trên host đó nên kế hoạch vẫn tiếp tục hợp lệ
    • Việc triển khai chỉ diễn ra khi đã giữ được khóa trên mọi host bị ảnh hưởng bởi thay đổi
    • Nếu ref đã cũ và có thứ khác đã được triển khai lên host, kế hoạch sẽ trở thành stale và bị hủy
    • Sau đó chỉ cần cập nhật ref ở local là có thể thấy kế hoạch mới nhất trong lần chạy tiếp theo
  • Khởi động lại systemd unit

    • Mọi dịch vụ đều chạy dưới dạng systemd unit và khởi động nhanh, nên khi không chắc thì tác giả chọn cách khởi động lại
    • Khi cấu hình ứng dụng thay đổi, các systemd unit bị ảnh hưởng sẽ được khởi động lại
    • Nếu unit không khởi động được, symbolic link sẽ được trả về phiên bản known-good trước đó rồi khởi động lại lần nữa, nhờ đó có thể rollback tự động ở mức mili giây

Mô hình đồng thời lạc quan

  • Triển khai của Deptool có yếu tố đồng thời lạc quan
  • Công cụ lập kế hoạch dựa trên giả định rằng nó biết trạng thái hiện tại của cụm, và nếu giả định đó sai thì phải thử lại
  • Khi không có tranh chấp, nó rất nhanh; đây chính là trường hợp của hạ tầng cá nhân do một người triển khai từ cùng một laptop
  • Trong môi trường nhiều người liên tục cố triển khai, chỉ một người thành công còn những người khác phải thử lại, nên hiệu năng có thể rất tệ
  • Mô hình này giống git push; nó không mở rộng tới quy mô hàng trăm người hay hàng nghìn server, nhưng là quá đủ cho hạ tầng cá nhân
  • Khi tự làm công cụ, bạn có thể tối ưu hóa đúng theo use case của mình

Xây dựng agent

  • Flatcar Linux và các ràng buộc của host ban đầu

    • Web server chạy trên Flatcar Linux
    • Flatcar Linux là OS dựa trên image với userspace rất nhỏ; có coreutils và Bash nhưng không có Python hay trình quản lý gói
    • Điều này tốt cho việc giảm bề mặt tấn công nhưng lại bất tiện khi muốn cài thêm thứ gì đó
    • Nếu công cụ muốn hoạt động mà trước tiên phải cài thứ gì đó, thì lại phát sinh thêm bài toán mới: tự động hóa chính quá trình cài đó
  • Chỉ dùng SSH như lớp truyền tải

    • Vì cần quản lý host mới từ bên ngoài, có thể dùng SSH và passwordless sudo
    • Cách chạy lệnh trực tiếp qua SSH không chỉ chậm vì handshake mà còn khiến argv không đi qua ranh giới SSH một cách an toàn, đồng thời phải xử lý các vấn đề word splitting và escaping của shell-over-SSH
    • Deptool chạy một chương trình đơn không đối số ở vị trí có thể dự đoán được và dùng chương trình này làm agent
    • Agent đọc thông điệp từ stdin và trả lời qua stdout
    • SSH chỉ được dùng như phương tiện truyền tải kiểu socket, nên input do người dùng kiểm soát không bao giờ đi vào lệnh SSH hay shell, qua đó tránh các vấn đề escaping
  • Dùng binary tĩnh

    • Agent được build thành binary tĩnh
    • Nó không giả định có gì ngoài kernel, và cũng không cần interpreter phải parse vài MB mã trước khi làm việc hữu ích
    • Ngay cả sau khi mitigate nhược điểm tệ nhất, Ansible vẫn phải gửi vài MB module Python cho mỗi lần kết nối; mà Flatcar thì thậm chí không có Python
  • Đặt binary theo đường dẫn dựa trên commit

    • Binary agent được lưu trong đường dẫn chứa commit đã build ra nó
    • Cách này đảm bảo hai đầu kết nối cùng chạy đúng một phiên bản, tránh phát sinh vấn đề tương thích giao thức
    • Đường dẫn có dạng /var/lib/deptool/bin/deptool-<version>-<commit>
  • Trước tiên giả định binary đã tồn tại

    • SSH handshake tốn kém nên không nên lãng phí vào bước probe hay cài đặt idempotent
    • Binary agent chỉ khoảng 1,6MB, không lớn đến mức cấm truyền nhưng cũng không phải miễn phí
    • Thay đổi cấu hình cụm xảy ra thường xuyên hơn nhiều so với cập nhật Deptool, nên thông thường có thể giả định binary đã sẵn có
  • Nếu chạy thất bại thì cài binary

    • Nếu khởi động binary thất bại, công cụ sẽ dùng kết nối SSH thứ hai để cài đặt
    • Lệnh chạy như sau
uname -sm
&& sudo mkdir -p /var/lib/deptool/{bin,apps,store}
&& sudo dd status=none of=<remote_bin_path>
&& sudo chmod +x <remote_bin_path>
&& sudo sha256sum <remote_bin_path>
  • Trước tiên, công cụ đọc một dòng từ stdout để lấy kết quả uname, từ đó xác định OS và kiến trúc CPU rồi gửi binary agent phù hợp với nền tảng đó
  • Khi ghi binary vào stdin, dd ở phía remote sẽ ghi nó xuống đĩa
  • Cuối cùng, công cụ đọc thêm một dòng từ stdout để kiểm tra shasum được tính trên máy remote và xác minh rằng việc truyền đã thành công
  • Toàn bộ quá trình này chỉ phụ thuộc vào các chương trình coreutils đã được chuẩn hóa
  • Sau đó thử chạy lại agent là sẽ thành công; agent cũng sẽ dọn các phiên bản cũ để tránh đầy đĩa

Hiệu quả và chi phí của cách làm bằng agent

  • Có được cách chạy agent và giao tiếp với host từ xa
  • Có thể tự động cài đặt trên host từ xa mà không cần gì ngoài coreutils
  • Vì hai phía cùng chạy một phiên bản nên tính tương thích giao thức được đảm bảo ngay từ thiết kế
  • Input do người dùng kiểm soát chỉ đi qua socket dựa trên SSH chứ không đi vào lệnh SSH hay shell, nên tránh được các vấn đề escaping và giới hạn độ dài
  • Trong trường hợp thông thường, chỉ cần một lần SSH handshake nên độ trễ thấp
  • Trong các trường hợp ít gặp như triển khai lên máy mới hoặc ngay sau khi cập nhật công cụ, sẽ cần thêm 2 kết nối và một lần truyền 1,6MB
  • Dùng ControlMaster có thể bỏ qua phần lớn chi phí của các kết nối sau, nên tổng chi phí chỉ ở mức vài giây
  • Trong trường hợp đó thì không còn là triển khai dưới 1 giây, nhưng theo tác giả vẫn tốt hơn Ansible
  • Trong quy trình triển khai cấu hình, chỉnh sửa một chút rồi triển khai lại, SSH có thể giữ kết nối nền nên việc triển khai cho cảm giác gần như tức thì

Kết quả sử dụng và công bố

  • Deptool đã được dùng trong tháng vừa qua để quản lý hạ tầng cá nhân
  • Điều đáng thích là có thể xem ngay kế hoạch chính xác trước khi kết nối và có rollback tự động, nhưng thay đổi lớn nhất là triển khai dưới 1 giây
  • Nếu một cách triển khai đúng đắn mất vài phút, người ta sẽ muốn chỉnh file trực tiếp trên server để rút ngắn vòng phản hồi; nhưng với Deptool, sửa ở local rồi triển khai còn nhanh hơn cả SSH vào server để mở editor
  • Cách ít ma sát nhất trở thành cách đúng đắn nhất, và mọi chỉnh sửa đã áp dụng đều được lưu trong lịch sử Git
  • Nếu lỡ làm hỏng thứ gì đó, Deptool sẽ rollback trước cả khi người dùng kịp nhận ra nó đã hỏng
  • Deptool được tạo ra để giải quyết chính xác bài toán cá nhân của tác giả; việc nó không cố giải quyết mọi vấn đề triển khai cho tất cả mọi người lại chính là điều giúp nó phát huy trong use case này
  • Nó có thể đặc biệt hữu ích trên các hệ điều hành dựa trên image; mã nguồn được công khai trên CodebergGitHub, đồng thời cũng có manual chi tiết

1 bình luận

 
Ý kiến trên Lobste.rs
  • Thật sự rất mừng khi dự án này công khai rằng không đưa văn bản do LLM tạo ra vào đâu gần đây cả: not putting LLM-generated text anywhere near this
    Bản thân công cụ này cũng có vẻ được trau chuốt kỹ và thiết kế tốt, nhưng có lẽ tôi vẫn sẽ tiếp tục dùng NixOS thêm một thời gian nữa

  • Chắc chắn tôi sẽ thử dùng. Nó trông như một phiên bản được hoàn thiện hơn của hệ thống tự làm mà tôi dùng để triển khai các dịch vụ dựa trên systemd
    Xem hướng dẫn thì thấy khá ổn, nhưng tôi thắc mắc nên xử lý trạng thái cục bộ thế nào. Ví dụ, tôi không tìm thấy trong tài liệu chỗ nên lưu cơ sở dữ liệu sqlite của ứng dụng ở đâu
    Tôi cũng muốn biết liệu có cách chuyển binary của ứng dụng lên server để systemd unit sử dụng hay không. Nếu không có thì việc triển khai binary được xử lý như thế nào

    • Nếu là phía server thì thường cứ đặt ở nơi bạn vẫn hay lưu, vị trí chuẩn là /var/lib/<yourapp>
      Nếu chạy ứng dụng dưới dạng systemd unit, bạn có thể dùng StateDirectory= để systemd tạo thư mục với quyền sở hữu đúng của người dùng
    • Chỉ riêng Deptool thì không chuyển binary
      Các ứng dụng tôi vận hành được build thành các image EROFS nhỏ bằng script dựa trên Nix này, và script đó cũng bao gồm cả chức năng đẩy image lên server. Trước đây đây là một bước riêng, nhưng giờ tôi gộp build và push thành một bước, rồi đặt vào các thư mục duy nhất để nhiều phiên bản có thể cùng tồn tại
      Kết quả build cũng bao gồm JSON chứa đường dẫn tệp, tôi nhập nó vào cấu hình cluster, render thành systemd unit rồi triển khai bằng Deptool. Tức là một công cụ phụ trách triển khai image, còn Deptool phụ trách kích hoạt
      Nếu dùng container thì thông thường bạn sẽ push lên registry, và phía server chỉ có tệp cấu hình chỉ định cần lấy gì, nên phần đó có thể được quản lý chỉ bằng Deptool
  • Một cách tiếp cận khác là dùng bootable containers, cũng khá ổn
    Điều còn thiếu là chưa có thứ gì thực sự chạy bootc update --apply trên đúng host một cách phù hợp. Có cơ chế tự động cập nhật, nhưng nó không được điều phối nên không phải cách mong muốn trong cluster
    Hiện giờ tôi vẫn làm thủ công, nhưng rốt cuộc thứ cần chạy cũng chỉ là một lệnh bootc nên sau này có vẻ sẽ dễ script hóa

  • Mỗi khi có công cụ triển khai mới xuất hiện tôi thường hơi hoài nghi, nhưng cái này có vẻ thiết kế tốt và được trau chuốt kỹ
    Việc dùng trực tiếp lệnh ssh cũng có vẻ là lựa chọn đúng. Người dùng biết rằng ssh họ đang có thực sự hoạt động, và có thể họ đang dùng cấu hình rất đặc biệt hoặc một binary ssh đã được vá riêng
    Những công cụ cố tự triển khai ssh bằng thư viện bên ngoài có khả năng sẽ gây cản trở cho một số người dùng

  • Tôi muốn biết chi tiết hơn về việc dùng EROFS như thế nào và vì sao lại dùng nó

    • Tôi dùng nó để triển khai Nginx và một vài ứng dụng lên Flatcar, gần giống với cách mọi người dùng OCI image cho mục đích này
      Flatcar không có trình quản lý gói, nên phải tự đưa phần mềm và phụ thuộc lên bằng cách nào đó, và image hệ thống tệp tự chứa là một trong những cách đó
      Với OCI image, một công cụ riêng như Podman hay Docker phải giải nén tar ở đâu đó và dựng một chồng overlay mount, còn nếu đã là image hệ thống tệp thì có thể chạy trực tiếp từ systemd unit bằng RootImage=
      Tôi build image bằng Nix nên nó thực sự chỉ chứa thành phần tối thiểu. Chỉ có binary Nginx, LibreSSL, libc và một vài thư viện dùng chung, thậm chí không có Bash
      Đây là một phần của phòng thủ nhiều lớp. Ngay cả khi Nginx có lỗ hổng thực thi mã từ xa, kẻ tấn công cũng chạy trong một namespace hệ thống tệp gần như không có gì để tiếp tục khai thác ở bước sau, và toàn bộ hệ thống tệp là chỉ đọc. Không chỉ vì nó được mount chỉ đọc, mà vì EROFS vốn không có khả năng ghi
      Trước đây tôi dùng Squashfs và nó hoạt động tốt, nhưng hệ thống tệp đó được thiết kế theo thời live CD. EROFS chọn những điểm đánh đổi phù hợp hơn với hệ thống ngày nay, dù thành thật mà nói với nhu cầu của tôi có lẽ sẽ không có khác biệt đo được
      Image có nhỏ hơn, nhưng đó là vì tôi dùng thiết lập nén khác. Về lý thuyết, EROFS phù hợp hơn với content-defined chunking nếu muốn tái sử dụng dữ liệu giữa các image khác phiên bản, nhưng tôi vẫn chưa thực sự dùng điều đó khi truyền image
  • Tình cờ bài này xuất hiện đúng lúc tôi đang bàn với bạn về một chiến lược triển khai đơn giản, và nó khá gần với kết luận mà bọn tôi đang đi đến
    Tuy vậy, tôi thắc mắc trong cấu hình này thì quản lý secret được làm thế nào

  • Bài viết nói “Prompting the deployment tool I wish I had”, nhưng
    https://codeberg.org/ruuda/deptool/…
    Ở một nghĩa nào đó thì việc các số dấu chấm động thuyết phục được người ta dùng Rust cũng khá ấn tượng

    • Nếu tạo mã bằng LLM thì ở nhiều khía cạnh tôi lại thích Rust hơn
      Nói theo hướng tích cực thì Rust là một ngôn ngữ “có kỷ luật”, với quy ước và hệ công cụ rất mạnh. Cả hai điều đó đều giúp ích cho LLM
      Kỳ lạ là, ít nhất nếu được định hướng một chút, LLM có xu hướng tạo ra chương trình ngắn hơn bằng Rust so với một số ngôn ngữ khác. Dù sao tôi cũng định đọc và chỉnh lại toàn bộ mã, nên với tôi ngắn hơn là tốt hơn
  • Tôi muốn biết với cái này thì secret được xử lý ra sao. Có workflow ưa thích nào không, hay là đưa vào image EROFS hoặc inject bằng systemd?

    • Hiện tại secret chỉ là chứng chỉ TLS, nó chỉ cần nằm trên một server và Lego đặt trực tiếp vào đó
      Thư mục đó được mount đọc-ghi trong Lego unit, và mount chỉ đọc trong Nginx unit