- Nếu môi trường của bạn đã dùng ảnh Docker như đơn vị triển khai, Nix là một điểm vào để thử nghiệm build có tính xác định và cố định phụ thuộc mà không phải thay đổi lớn quy trình làm việc
docker build thông thường dễ phụ thuộc vào trạng thái Internet công khai như kho Ubuntu hay các bản tải bên ngoài, nên sau một thời gian sẽ khó tái tạo lại đúng cùng một ảnh
- Nix biết các phụ thuộc cần thiết ở cấp độ gói, nên với
dockerTools.buildLayeredImage có thể cấu hình để chỉ tải lên lại những layer đã thay đổi
- Ví dụ
douglas-adams-quotes dựa trên Go cho thấy có thể tạo binary bằng pkgs.buildGoModule, rồi dùng nix build.#docker, docker load <./result để nạp và triển khai như một ảnh Docker thông thường
- Khi nhiều dịch vụ trong monorepo chia sẻ layer và bộ nhớ đệm Nix, có thể giảm thời gian và chi phí build trong các lần tái tạo ảnh từ commit cũ và triển khai lặp lại
Vì sao gắn Nix vào quá trình build ảnh Docker
- Nix đồng thời mang ba tính chất: trình quản lý gói, ngôn ngữ, và hệ điều hành
- Dùng ngôn ngữ Nix để viết chỉ thị build gói
- Trình quản lý gói Nix dựa trên các chỉ thị đó để tạo phần mềm, công cụ, ảnh NixOS, ảnh container, v.v.
- Đưa Nix vào pipeline CI/CD hiện có không hề dễ
- Nix khác với cách làm mà nhiều lập trình viên đã quen
- Nếu không phải môi trường có thể bắt đầu lại mọi thứ từ đầu như startup mới hay homelab, rào cản áp dụng là khá lớn
- Với các tổ chức đã dùng Docker, có thể thử áp dụng Nix từ khâu build ảnh container mà không phá vỡ đáng kể luồng triển khai hiện có
Những điểm dễ lung lay của Docker build
- Docker và container hóa đã được chấp nhận rộng rãi, đến mức ảnh Docker được dùng như định dạng gói phổ thông trên Internet
- Các nền tảng như Fly.io, Railway, Render dùng ảnh Docker thay vì ảnh VM tùy ý hay chương trình Linux dạng tarball
- Docker build không phải lúc nào cũng có tính xác định
- Phần lớn Dockerfile ngoài Internet đều build được, nhưng một tỷ lệ lỗi nhỏ vẫn có thể dẫn đến sự cố vận hành
- Một trong những điểm yếu lớn nhất là Docker build truy cập Internet công khai
- Điều này cần thiết để tải gói từ kho Ubuntu
- Khi cần tái tạo lại cùng một ảnh sau này, rất khó khôi phục đúng trạng thái của kho Ubuntu tại thời điểm đó
- Ubuntu 18.04 sắp hết hỗ trợ trong năm nay, và có thể chỉ khi xảy ra sự cố mới biết thứ gì đang phụ thuộc vào phiên bản đó
- Cách đơn giản để thêm gói vào ảnh Docker có thể tạo ra không gian lãng phí
- Nếu chạy
apt-get upgrade ở đầu quá trình build, các tệp bên trong ảnh container có thể bị thay thế
- Các tệp trước khi bị thay thế có thể vẫn nằm lại trong layer và tích tụ như các bản sao bị che khuất
Cách Nix xử lý các layer Docker
- Nix biết trước các phụ thuộc cần thiết và có thể chia chúng thành số lượng layer Docker ít nhất có thể
- Chỉ vì thay đổi một dòng mã không có nghĩa
apt hay npm phải cài lại phụ thuộc
- Chỉ phần thay đổi tối thiểu thực sự mới cần được phản ánh vào bản cập nhật ảnh
dockerTools là bộ công cụ để đưa các gói Nix và phụ thuộc của chúng vào ảnh Docker
- Ảnh Docker tạo bằng Nix nhìn chung chia thành hai cách
- Ảnh không phân layer: đặt chương trình, phụ thuộc, các mục bổ sung như chứng chỉ gốc TLS vào một thư mục rồi phơi bày thành ảnh một layer
- Ảnh phân layer: đặt từng phụ thuộc vào layer ảnh riêng để chỉ phải tải lên phần thực sự thay đổi
- Docker cũng có kho lưu trữ dựa trên nội dung ở bên trong, nhưng chỉ với
docker build thì khó tận dụng đầy đủ
- Ảnh phân layer của Nix dùng layer theo từng gói, nên các phụ thuộc như
glibc chỉ cần tải lên một lần
- Tuy nhiên nếu thư viện đó cần sửa đổi thì layer tương ứng vẫn phải được tải lên lại
Ví dụ dịch vụ Go: Douglas Adams Quotes
- Dịch vụ ví dụ là một chương trình Go cung cấp các câu trích dẫn của Douglas Adams
- Với dự án Go module, gói Nix được định nghĩa bằng
pkgs.buildGoModule
bin = pkgs.buildGoModule {
pname = "douglas-adams-quotes";
inherit version;
src = ./.;
vendorHash = null;
};
- Định nghĩa này dùng mẫu Go module để cấu hình trình biên dịch Go, trình biên dịch C cho CGo, và tải các phụ thuộc bên ngoài
pname là tên gói
version được tạo tự động từ commit Git của dịch vụ
src = ./.; dùng mã nguồn trong thư mục làm việc hiện tại
- Nếu không có phụ thuộc ngoài thư viện chuẩn thì có thể để
vendorHash = null
- Nếu có phụ thuộc bên ngoài, có thể chỉ định hash của toàn bộ phụ thuộc hoặc dùng
gomod2nix
- Gói được build bằng lệnh sau
nix build .#bin
- Ảnh Docker phân layer có thể được tạo bằng
dockerTools.buildLayeredImage
docker = pkgs.dockerTools.buildLayeredImage {
name = "registry.fly.io/douglas-adams-quotes";
tag = "latest";
config.Cmd = "${bin}/bin/douglas-adams-quotes";
};
- Khi chỉ định binary máy chủ đã build trong
config.Cmd, các thành phần cần thiết sẽ được sao chép kèm theo
glibc và các mục cần thiết để chạy cũng được bao gồm
- Có thể thêm các gói bổ sung như chứng chỉ gốc CA vào
contents
docker = pkgs.dockerTools.buildLayeredImage {
name = "registry.fly.io/douglas-adams-quotes";
tag = "latest";
contents = with pkgs; [ cacert ];
config.Cmd = "${bin}/bin/douglas-adams-quotes";
};
- Ảnh được tạo và nạp vào Docker daemon bằng các lệnh sau
nix build .#docker
docker load < ./result
- Nếu mở bằng
dive, có thể thấy mỗi layer thêm các gói khác nhau từ nixpkgs, và cuối cùng các mục được liên kết tượng trưng vào root của ảnh
Hiệu quả của việc chia sẻ layer trong monorepo
- Nếu có nhiều dịch vụ trong cùng một kho, các ảnh Docker tạo bằng Nix có thể chia sẻ layer
- Việc này diễn ra mà không cần cấu hình riêng
- Nếu chỉ làm bằng Docker, thường phải tạo nhiều base image dùng chung, và một số dịch vụ có thể chứa công cụ hay nội dung không cần thiết mà chúng không dùng
- Ví dụ, kho
Xe/x là một monorepo tập hợp 10 năm side project, thử nghiệm và công cụ
- Nhiều dự án trong đó đang được triển khai trên khoảng ba nền tảng
- Nhằm hội tụ về một nền tảng triển khai chung, tác giả đã tiến hành đóng gói chúng thành ảnh Docker
- Khi đẩy bản cập nhật của một dịch vụ, một phần cập nhật được chia sẻ với phần lớn các dịch vụ khác cũng sẽ được đẩy theo
- Cách này giúp tiết kiệm thời gian và chi phí trên nhiều dự án
Những gì khó đạt được chỉ với cache của Docker
- Về lý thuyết, dùng cơ chế cache của Docker cũng có thể build ảnh hiệu quả gần như Nix
- Nhưng để đạt hiệu quả tương tự theo cách của Docker, các bước build có thể trở nên khó bảo trì
- Cần cài thư viện dùng chung vào layer riêng
- Phải bật lại network stack trong lúc build nên khả năng tái lập kém hơn
- Phải điều chỉnh lại search path, cờ trình biên dịch và cấu hình liên quan đến CGo
dockerTools.buildLayeredImage của Nix xử lý thay các chi tiết khi đưa gói vào container, giúp cấu hình đơn giản hơn
Tái tạo trạng thái quá khứ và bộ nhớ đệm Nix
- Một trong những ưu điểm lớn của Nix là có thể build lại chính xác phần mềm ở một thời điểm trong quá khứ
- Khi cần tái hiện lỗi ở môi trường của một khách hàng sau này, có thể dựng lại cùng một ảnh Docker
- Build gói có vai trò cố định toàn bộ trạng thái của phần mềm và các phụ thuộc tại một thời điểm
XeDN là dự án đã được phát triển trong vài năm, và phiên bản từ 14 tháng trước có thể được build bằng lệnh sau
nix build github:Xe/x/567fdc2#xedn-docker
- Lệnh này build mục tiêu
xedn-docker từ một commit cụ thể của GitHub repo Xe/x
- Nếu nạp vào Docker daemon, ảnh tạo ra sẽ có cùng từng byte như thời điểm đó
- Ví dụ này cũng bao gồm Go 1.19
- Nếu chỉ dùng Docker build thông thường để tái tạo tương tự, có thể sẽ cần rất nhiều chi phí lưu trữ
- Bộ nhớ đệm Nix lưu đầu ra của các lệnh Nix để không phải build lại sau này
- Mỗi laptop của lập trình viên không cần tự build lại các gói như
nokogiri
- Có thể nhận kết quả đã được build sẵn trên đám mây
- Garnix được dùng làm CI cho mọi dự án flakes và báo cáo trạng thái build theo từng commit
- Cấu hình máy homelab cũng được build bằng Garnix, nên khi cập nhật mỗi tối sẽ lấy cấu hình mới nhất từ bộ nhớ đệm Garnix thay vì tự build
Kết luận với tư cách là đầu ra triển khai
- Có thể xem Nix là một trình dựng ảnh Docker tốt hơn chính trình dựng ảnh của Docker
- Nix buộc bạn chỉ định kết quả mong muốn, thay vì phải tự liệt kê chi tiết các bước để đi tới kết quả đó
- Trong môi trường đã dùng Docker, áp dụng Nix bắt đầu từ khâu build ảnh Docker là một lộ trình triển khai thực tế
- Ảnh Docker tạo bằng Nix có thể chia sẻ layer giữa nhiều phần trong monorepo
- Nhờ bộ nhớ đệm nhị phân, không cần build lại mã đã từng được build trong quá khứ
- Đầu ra cuối cùng vẫn là một ảnh container thông thường có thể triển khai lên các nền tảng như AWS, Google Cloud, Fly.io
1 bình luận
Ý kiến trên Hacker News
Tôi đã nhiều lần cố gắng thích Nix, nhưng giờ cảm giác là nên bỏ cuộc
Tôi có 2 hệ thống chạy Nix và sợ động vào chúng; trước đây cả hai từng hỏng đến mức tôi phải cài lại từ đầu. Về lý thuyết, Nix có tính lũy đẳng và tất định, nhưng trên thực tế bạn phải hiểu nó tất định đối với điều gì, và nếu không hiểu sâu hành vi của mọi phần mà nó phụ thuộc vào, bạn sẽ gặp các kết quả kỳ lạ, các lỗi quái dị chẳng giúp ích gì, hoặc thậm chí không có phản hồi nào
Tài liệu, dù đầy đủ và đúng về mặt kỹ thuật, vẫn khó hiểu một cách khủng khiếp, còn tutorial thì chỉ đưa bạn đi được khoảng 80% chặng đường. Ra ngoài phạm vi đó thì bạn phải tự dựng lấy, mà việc đó đòi hỏi nhiều năm kinh nghiệm và thất vọng, điều tôi không còn muốn chịu nữa. Điểm mạnh của Docker lại chính là sự hỗn loạn đó: chỉ cần biết sơ sơ về shell và trình quản lý gói của distro là gần như có thể làm được bất cứ thứ gì, và khi có vấn đề, nhờ các công cụ đã tồn tại hàng chục năm, việc chẩn đoán và sửa lỗi dễ hơn nhiều
Nix giống Emacs ở chỗ, nếu có đủ kiên nhẫn cùng kiến thức sâu và mang tính tầm nhìn, bạn có thể làm bất cứ điều gì; nhưng có vẻ hoặc là phải dấn thân hoàn toàn, hoặc chỉ đứng từ xa quan sát
Build liên tiếp hai lần trên cùng một máy thì sẽ ra cùng kết quả, nhưng theo thời gian, khi trình quản lý gói và tag của base image được cập nhật, kết quả build lại sau một tháng có thể hoàn toàn khác. Khi nhóm phải quản lý khoảng 40 container, việc sửa container trở thành một phần lớn của công việc
Vì vậy về lý thuyết Nix lẽ ra phải hoàn hảo, nhưng nó quá khác biệt: công cụ của vendor không chạy được trên Nix, và khi gặp lỗi cũng khó tìm nhanh lời giải trên web. Vì sự bức bối đó tôi đã tạo https://www.stablebuild.com, cung cấp build tất định dựa trên Docker trên các container Ubuntu, Debian, Alpine. Nó gồm cache pull-through bất biến cho Docker Hub, bản sao đầy đủ hằng ngày của các kho gói Ubuntu/Debian/Alpine, bản sao hằng ngày của các PPA phổ biến và chỉ mục PyPI, cùng cache bất biến cho tệp/URL tùy ý
Trong thực tế, nó dễ dùng, dễ debug và có khả năng tương thích phần mềm rộng, nên với các container đã chuyển sang StableBuild, số vấn đề phát sinh do tính không tất định là 0
Tôi từng mất thời gian để hiểu vì sao
nix developkhông hoạt động như tài liệu mô tả, và làm sao để nó hoạt động như vậy. Tài liệu nói rằng mặc định bạn sẽ vào môi trường tại thời điểm build, nhưng mặc định thực tế lại không được đóng kín, tên các tùy chọn dòng lệnh cũng gây nhầm lẫn, nên tôi phải đào kiến thức từ mã nguồnTôi cũng mong các edge case phá vỡ tính tái lập được nêu rõ hơn. Mã dấu phẩy động nhạy với thứ tự phép toán, và trạng thái có thể rò rỉ do cơ chế preempt của hệ điều hành; nếu ngay cả những điều hiển nhiên cũng không được nói rõ, mọi người sẽ tự bắn vào chân mình
Nếu dùng thiết lập “Erase your darlings”, có thể loại bỏ hầu hết trạng thái không thể tái lập bên ngoài tài khoản người dùng. Hơi phiền, nhưng trong NixOS thì có gì mà không phiền đâu
https://grahamc.com/blog/erase-your-darlings/
Tôi không bận tâm phần bên trong tài khoản người dùng, và cũng không thích Home Manager
Docker thì ổn, nhưng trên Mac hiệu năng không tốt lắm; còn với Nix, việc cài đặt và khiến mọi thứ hoạt động theo cùng một cách trên nhiều máy là chuyện rất đơn giản nên tôi thích điểm đó. Tài liệu Nix tệ thật, nhưng ChatGPT-4 rất hữu ích trong việc xử lý vấn đề với Nix
Cảm giác 90% vấn đề với Nix nảy sinh vì cố làm những việc không theo “cách của Nix”
Tôi đã dùng gần như mọi trình quản lý gói của các distro và bằng cách nào đó đều từng làm chúng hỏng, dù không làm gì đặc biệt. Fedora Kinoite vẫn chịu được việc thêm/xóa layer, cập nhật hằng ngày, thậm chí rebase từ Silverblue. Cá nhân tôi nghĩ rpm-ostree có thể sẽ thay thế Nix
Tôi xem Nix và NixOS đang ở trạng thái tương tự như git trước GitHub
Ý tưởng cốt lõi dựa trên khoa học máy tính nghiêm túc hơn so với các cách làm hiện có như SVN hay Docker; phần “đường ống” bên trong vẫn còn vấn đề nhưng không tệ hơn, còn các công cụ và tài liệu hướng tới người dùng thì chưa đạt mức được chấp nhận rộng rãi
Việc flox ra mắt có thể đã thay đổi điều này: https://flox.dev. Gần như mượt mà, và cũng không ngạc nhiên vì họ đến từ DE Shaw
Nix không có nhiều ý nghĩa nếu thiếu flakes và
nix-command, nhưng cả hai đều được tài liệu ghi là thử nghiệm và mặc định cũng bị tắt. Tài liệu đang tốt hơn nhưng vẫn còn thiếu, còn nixlang thì rất hay nếu học kỹ, nhưng không thể chấp nhận như một rào cản gia nhập cho số đông.nix-env -iA foothực tế trong đa số trường hợp không phải là hành vi bạn muốn, nên Nix giống một vũ khí bí mật cho các công ty đủ sức duy trì chuyên môn nội bộ hơn là một trình quản lý gói theo cách nó được quảng báflox hạ thấp rào cản để “dùng thử một lần là có ngay trải nghiệm tốt hơn”. Nix/NixOS, hoặc một thứ tương tự, cuối cùng sẽ đẩy Docker lùi lại như Subversion, nhưng điều đó sẽ không xảy ra cho đến khi có khoảnh khắc kiểu GitHub, rồi sẽ xảy ra cùng một lúc
Phần lớn các phàn nàn về Nix trong thread này về mặt kỹ thuật là sai, nhưng hoàn toàn dễ hiểu, và quan trọng hơn là không phải lỗi của người dùng. Tôi biết thế hệ từng biết một thế giới không có git/GitHub đang ít dần, nhưng sẽ đáng nghe Linus giải thích trước những người vượt qua tiêu chuẩn tuyển dụng thời kỳ đầu của Google rằng tại sao họ nên quan tâm đến một công cụ mà họ cảm thấy phức tạp
https://youtu.be/MjIPv8a0hU8?si=QC0UnHXRdMpp2tI4
Chúng tôi đã xây dựng nó một thời gian, và nếu có điều gì đáng ưu tiên để làm nó tốt hơn thì tôi rất muốn nghe
git làm được như vậy, còn Nix thì chưa, nên tôi không chắc một khoảnh khắc kiểu GitHub có giúp được không
Bài blog này thiếu phần giải thích vì sao các layer Docker dùng chung lại hữu ích
Lý do là caching: càng nhiều image dùng chung cùng một layer thì càng cache được nhiều thứ hơn, nhờ đó container khởi động nhanh hơn
Docker yếu ở điểm này vì để hưởng lợi từ caching, khi build image bạn cần tạo ra các layer giống với layer hiện có nhiều nhất có thể. Ví dụ nếu hôm nay chạy
apt-get install python3mà không có bản cập nhật mới nào, lẽ ra phải tạo ra layer chính xác giống hệt hôm qua, nhưng Docker layer được cache bằng hash của file, nên mọi file phải giống hệt nhau, kể cả metadata như thời điểm tạoNix vốn đã lưu dependency bằng hash, nên với cùng phiên bản và cùng cấu hình, layer sẽ luôn giống nhau
Dependency thường tạo thành graph chứ không phải tree, nên việc này nhanh chóng trở nên phiền phức. Các công cụ thay thế như Nix, và có lẽ Bazel, không có ràng buộc này và có thể ánh xạ dependency graph vào Docker layer để đạt caching chi tiết. Đây là cách không thể biểu đạt bằng Dockerfile
apt-getsẽ không tự động bị vô hiệu hóaNó chỉ thay đổi khi dùng
--no-cachehoặc khi có thay đổi ở layer phía trênGần đây tôi đã vật lộn 2–3 ngày để build image Docker trên Darwin, nên bài này khiến tôi có cảm giác như vũ trụ đang trêu ngươi mình
Nix rõ ràng là công cụ tốt nhất cho mục tiêu mong muốn, nhưng nó có những góc tối bị bỏ hoang hút cạn linh hồn. Tôi thích nó, nhưng đôi khi có cảm giác như mình là Morty bị kéo vào chuyến phiêu lưu Compilerland của Rick
Về bản chất, Docker gần như là một nhà tù chứa các binary Linux. Trên Linux, bạn build binary rồi đưa vào container là xong, và mã Nix cũng đơn giản. Nếu build được code thì tạo container chỉ là thêm một bước nữa
Nhưng Docker yêu cầu binary Linux, còn trên Mac thì Docker Desktop khởi chạy một VM Linux, thực hiện mọi việc trong đó rồi che giấu sự thật ấy. Nix không làm như vậy, nên có hai lựa chọn
Thứ nhất là cross-compile, nhưng khi đó phải cross-compile được cả glibc; phần lớn dependency cộng đồng có thể làm được, nhưng một số package có thể chưa được tác giả quan tâm đến cross-compile. Hơn nữa, Hydra — nơi lấp đầy cache Nix tiêu chuẩn — không build theo kiểu cross-compile, nên bạn có thể build rất lâu rồi thất bại
Thứ hai là gắn một Linux builder vào Mac và gửi các tác vụ build
x86_64-linuxsang đó. Có thể là máy vật lý, VM, thậm chí container Docker NixOS cũng đượcCách 1 trông có vẻ là cách đúng, nhưng cách 2 thực dụng hơn. Vấn đề bạn gặp có lẽ nhiều khả năng là do đang thử cách 1, và việc này đòi hỏi nhiều kinh nghiệm không chỉ về Nix mà cả cross-compile. Sẽ tốt nếu Hydra cũng build cross-compile từ Darwin sang Linux để cung cấp cache và ngăn tình trạng vỡ build, nhưng chi phí cũng sẽ tăng. Có vẻ nên thử giải pháp số 2
Hình như từng có giải pháp chính thức: https://ryantm.github.io/nixpkgs/builders/special/darwin-bui...
Ở một số stack, cross-compile không thật sự vững chắc, nên tôi cross-compile
{aarch64,amd64} x {linux,darwin}bằng Docker. Tôi đang chạy nhiều Docker trên Darwin aarch64 để compile tất cả, và trải nghiệm khá tốthttps://github.com/gytis-ivaskevicius/high-quality-nix-conte...
Có một hai bug, nhưng chủ yếu có vẻ liên quan đến volume, còn build image Docker thì xử lý ổn. Phần gập ghềnh hơn là tốc độ: phần cứng cộng với việc Docker phải giả lập VM Linux khiến nó bị chậm đáng kể
Trải nghiệm build Docker image cho ứng dụng Java bằng Nix không được tốt lắm
Sau khi
gradle2nixbị bỏ, dường như không có lựa chọn thay thế rõ ràng nào cho các ứng dụng Java dựa trên Gradle. Trước đây tôi từng rủ một người bạn thử tạo Docker image nhỏ nhất có thể cho một ứng dụng Spring Boot đơn giản, nhưng kết quả tạo bằng Nix lại có kích thước gấp đôi image tạo không dùng NixCó thể xem mã tại đây: https://github.com/jossephus/Docker_challenge/blob/main/flak...
Cả zulu và JDK mà gradle đưa vào qua đối số
jdkđều được kèm theo. Nhìn vàogradleGentrong nixpkgs sẽ hiểu ý này. Xin lỗigradle2nix, tôi đang làm một bản cải thiện ít kiểu hack hơnĐại khái là như thế này: https://gist.github.com/takeda/17b6b645ad4758d5aaf472b84447b...
Tôi link mọi thứ với musl, khi biên dịch Python thì tắt toàn bộ các package ứng dụng không dùng đến, và loại bỏ những phần không dùng trong boto3/botocore. Chỉ riêng boto3/botocore đã hơn 100MB
Các package Nix về cơ bản nhắm tới hệ điều hành NixOS, nên trong bối cảnh thông thường có nhiều dung lượng đĩa, người ta muốn bật đầy đủ tính năng. Vì vậy kéo theo rất nhiều dependency không cần thiết. Ngược lại, Alpine image được thiết kế cho Docker, mục tiêu là tắt các tính năng phụ trong package, nên kết quả nhỏ hơn
Muốn tạo image nhỏ thì phải dùng
overrideđể tắt những thứ không cần. Ví dụ zulu có các thứ như alsa, fontconfig, freetype, xorg, cups, gtk, cairo, ffmpeg. Bạn tôi đã cẩn thận chỉ trích xuất các file cần thiết rồi đưa vào container, còn phía Nix thì coi như đã bundle toàn bộ package zulu cùng mọi dependency của nóTrước hết cần sửa để chỉ đưa vào một JDK, rồi sau đó có thể giảm thêm kích thước JDK bằng các cách ở trên. Dùng
openjdk_headlesscó thể sẽ đơn giản hơnhttps://github.com/NixOS/nixpkgs/blob/master/pkgs/developmen...
https://github.com/GoogleContainerTools/jib/tree/master/jib-...
openjdk_headlessbỏ qua các dependency GTK và X mà Spring không cầnTôi chuyển từ Zulu sang dựa trên bản build OpenJDK headless của Nixpkgs để loại bỏ dependency vào thư viện GUI, rồi dùng
pkgs.jre_minimalvàjlinkđể tạo một JRE tối thiểu tùy chỉnhKích thước image là 161MB, lớn hơn một chút so với image
demo_jlink. Lý do là JRE khoảng 90MB vì thực sự bao gồm tất cả các module cần thiết để chạy ứng dụng. Lệnh gọijdepstrongDockerfile_jlinkkhông phát hiện được tất cả module, nên đang tạo JRE chỉ vớijava.base. Nếu JRE tối thiểu của tôi cũng chỉ đưajava.basevào thì kích thước JRE khoảng 50MB, và image container bị hỏng là 117MB theo PodmanTôi cũng đã loại bỏ
copyToRootsai trong lời gọidockerTools.buildImage. Chỉ riêng string context củaconfig.Cmdcũng đã đưa app vào image, còn mã cũ đang copy app thêm một lần nữa. Ngoài ra tôi đổi sangdockerTools.buildLayeredImageđể đưa từng store path vào từng layer image riêng; điều này tốt cho khả năng mở rộng dung lượng khi chia sẻ dependency giữa nhiều container image, nhưng không ảnh hưởng đến thử nghiệm với một image đơn lẻPhần còn lại chủ yếu là tối ưu kích thước JRE.
glibclà dependency lớn tiếp theo, khoảng 30MB, có vẻ vì Nixpkgs build glibc để hỗ trợ nhiều locale và bảng mã ký tự. Tôi không biết việc tách phần này thành derivation hoặc output riêng để có thể chọn dùng có khả thi và thực dụng hay không. Nếu tạo nhiều image bằngdockerTools.buildLayeredImage, với điều kiện dùng cùng một commit Nixpkgs, glibc và các dependency còn lại đều sẽ được chia sẻhttps://github.com/max-privatevoid/hackernews-docker-challen...
Nếu đã dùng Nix rồi thì rất tuyệt, và hy vọng quản lý gói theo kiểu khai báo như Nix hay Guix sẽ phổ biến rộng rãi hơn
Nếu bạn đang dùng Docker nhưng muốn đưa Nix vào dần dần, cũng có cách tiếp cận trong bài thuyết trình này: https://youtu.be/l17oRkhgqHE
Thay vì chuyển ngay toàn bộ cấu hình và phần build container sang Nix, bạn có thể giữ Dockerfile và để nó build cấu hình Nix. Nhược điểm lớn nhất là hoàn toàn không tận dụng được layer, còn ưu điểm là có thể thay đổi Dockerfile từng bước trong khi tái sử dụng hạ tầng hoặc tự động hóa Docker hiện có
[1] https://mitchellh.com/writing/nix-with-dockerfiles
Nhưng có lẽ phần lớn tính không tái lập đó là do không có gì đảm bảo một trong các layer Docker sẽ tiếp tục còn dùng được. Nếu vậy thì tôi cũng tò mò liệu Nix có đảm bảo rằng các phiên bản gói sẽ mãi mãi nằm trong kho lưu trữ hay không
Hơi lạc đề, nhưng họa sĩ minh họa Annie Ruygt, người vẽ hình cho slide này, cũng đã làm logo và art thương hiệu cho hai startup YC là RethinkDB(rethinkdb.com) và Pachyderm(pachyderm.io)
Cô ấy từng làm ở Pachyderm, còn Pachyderm là công ty do các cựu kỹ sư RethinkDB sáng lập. Tác phẩm của cô ấy thật sự rất tốt
Tương đối gần đây, theo đề xuất của đội hạ tầng, tôi đã dành khoảng nửa ngày để thử build image nền CI bằng Nix, nhưng image rất lớn và một phần không chạy được vì vấn đề liên kết
Điều đặc biệt khó chịu là khi tạo image đa kiến trúc, nó lại cố thực sự chạy thứ gì đó của kiến trúc khác, và chỉ hỗ trợ ảo hóa phần cứng bằng qemu. Máy build và workstation của tôi là VM nên không có cái đó, còn
binfmt-miscthì có. Nếu nó fork/execmkdirarm64 để tạo/tmpthì đã chạy được, nhưng layer Docker chỉ là file tar, nên có thể tạo thư mục như dưới đâyecho "tmp uid=0 gid=0 time=0 mode=0755 type=dir" | bsdtar -cf - @-Rất có thể layer chính xác này đã tồn tại đâu đó, nên người dùng thậm chí có thể không cần tải xuống
Mỗi lần thử Nix, tôi lại có cảm giác chỉ cần thêm vài tháng nữa là có thể dùng nó thường xuyên. nixpkgs có gần như mọi gói tôi muốn và cài trên workstation thì ổn. Nhưng yêu cầu “cần bash, python, build-essential, Bazel” trông không giống mục tiêu của một Docker image builder. Nếu chỉ để đưa một binary Go vào Docker image thì không cần Nix. Lấy distroless rồi bỏ ứng dụng vào file tar là thành container. Cá nhân tôi dùng
rules_ocicủa Bazel; bên trong nó cũng chỉ làm đến mức đó, cộng thêm chút thông minh để build binary cho nhiều kiến trúc, tạo YAML image index và push lên registryTất nhiên file build của gói phải hỗ trợ điều đó. Nói qemu chỉ hỗ trợ ảo hóa phần cứng cũng có vẻ không đúng. qemu vẫn dùng được cho cả những kiến trúc chỉ có thể mô phỏng bằng phần mềm
Ví dụ tối thiểu được đề cập ở đây: https://discourse.nixos.org/t/how-do-i-get-a-shell-nix-with-...
Tôi không định nói rằng nó phải đơn giản như chỉ dùng
pkgCross, nhưng tôi tò mò bạn đã gặp vấn đề cụ thể nào trong quy trình thông thườnghttps://nix.dev/tutorials/cross-compilation.html
Tôi đã dùng nó nhiều năm trong vài dự án, nhưng nếu kích thước là vấn đề thì có thể tạo image rất nhỏ chỉ chứa những gì được chỉ định theo cách bài viết đề cập
[1] https://hub.docker.com/r/nixos/nix/tags
Tôi còn dùng cả musl, build được những image nhỏ nhất so với bất kỳ công cụ nào khác, nhanh và nhất quán trong CI, cache cũng hoạt động tốt. Tôi không hiểu rõ là thứ gì đã được chạy
buildFHSEnvđể hỗ trợ các binary bên thứ ba kỳ lạ hay khôngCó vẻ Nix cần thêm nhiều bài viết giải thích cách chuyển đổi mềm mại, từng phần một, từ các hệ thống hiện có
Là kỹ sư nền tảng, tôi muốn thích Nix, nhưng nó không dễ với tất cả những người khác
Tôi cũng nghĩ trải nghiệm dành cho lập trình viên hiện vẫn khá tệ. Ví dụ, tôi thích trải nghiệm lập trình viên của devbox hơn vì có thể thêm gói như
devbox add python@3.11Nhìn vào
flake.nixdài 120 dòng thì cũng khó nói chính xác là “dễ hơn”https://github.com/Xe/douglas-adams-quotes/blob/main/flake.n...
Flake được liên kết định nghĩa cách build binary Go, định nghĩa Docker image dùng binary đó làm entrypoint, và còn định nghĩa một module NixOS tạo service systemd chạy binary Go. Ngoài ra còn bao gồm cả test NixOS để kiểm tra module NixOS đó có tạo service systemd đúng như mong đợi hay không
Framework test của NixOS khá ấn tượng, và các test chạy bên trong QEMU VM chạy kèm NixOS. Những phần liên quan đến bài viết được liên kết chỉ là định nghĩa binary Go và Docker image, cùng một phần boilerplate xung quanh
Ngoài ra, nhiều dòng tương ứng 1:1 với phần mô tả service systemd inline, nên dù dùng hệ thống nào thì chúng cũng không biến mất. Nó còn có một cách khá hay để khai báo nhiều hệ thống bằng override
Ví dụ này giống kiểu “hãy thử làm một việc nhỏ theo cách như trong một dự án lớn nghiêm túc”; nếu xử lý như việc dùng một lần thì tôi nghĩ có thể rút xuống khoảng 40 dòng
Binary đó và file cấu hình là dạng gần nhất với một “flat pack” mà bạn có thể có trên Linux, và ví dụ này cho thấy rõ chúng ta đã bỏ lỡ điều gì giữa rừng và cây
Guix cũng có một tùy chọn Docker dễ dùng và khá ổn là
guix pack -f dockerGuix còn có ưu điểm là dùng Guile/Scheme, một ngôn ngữ đã được sử dụng, thay vì một ngôn ngữ chuyên dụng riêng
[1] https://guix.gnu.org/manual/en/html_node/Invoking-guix-pack....