在討論 Nix 的時候,大家總是喜歡拿它和 Docker 做比較。但我認為 Nix 和 Docker 是解決不同問題的工具,前者是構(gòu)建和部署容器的工具包,而后者則是包和配置管理器。這兩個工具在功能上確實會有一些重疊之處,都可用于創(chuàng)建可重現(xiàn)的環(huán)境。所謂“可重現(xiàn)的環(huán)境”指的以相同的方式創(chuàng)建完全相同的新環(huán)境。也就是說,這些環(huán)境擁有相同的工具、版本和配置。
可重現(xiàn)的環(huán)境可以確保項目中的所有開發(fā)人員都擁有完全相同的工具集。此外,開發(fā)人員可以在與生產(chǎn)環(huán)境類似的環(huán)境中開發(fā)軟件,因此可以避免部署時發(fā)生意外。
Nix 和 Docker 這兩種工具都可以解決:“它(可重現(xiàn)環(huán)境)在我的機器上運行”的問題,不過,二者采用的方法卻不相同。
使用 Docker 構(gòu)建可重現(xiàn)的環(huán)境
Docker 提供了創(chuàng)建容器鏡像的工具。鏡像存儲了容器的內(nèi)容和配置,通常包含文件系統(tǒng)、一些環(huán)境變量和命令。
有了鏡像,你就可以在不同的機器上創(chuàng)建出功能完全相同的新容器。
你可以將鏡像分發(fā)給其他開發(fā)人員,這樣他們就可以構(gòu)建出相同的環(huán)境了,而且你也可以通過鏡像將服務部署到生產(chǎn)環(huán)境。
Docker 鏡像可以由 Dockerfile 創(chuàng)建。該文件告訴 Docker 運行構(gòu)建鏡像的命令,包括從宿主系統(tǒng)復制文件,或使用包管理器(如 apt 或 apk)安裝軟件包。
下面是一個 Dockerfile 的示例:
FROM node:12-alpine
RUN apk add --no-cache python g++ make
WORKDIR /app
COPY .
RUN yarn install --production
CMD ['node', 'src/index.js']
Docker 會創(chuàng)建一個新容器,并在其中構(gòu)建鏡像。在執(zhí)行 Dockerfile 中的每條命令之前,Docker都會創(chuàng)建一個新的文件系統(tǒng)層。Docker 使用的是聯(lián)合掛載(Union mount)文件系統(tǒng),這種系統(tǒng)可以將多個文件系統(tǒng)或目錄疊加起來,顯示為一個文件系統(tǒng)。
由于每條命令都有自己的文件系統(tǒng)層,因此 Docker 可以檢測哪些層仍然有效,而哪些層需要根據(jù)項目的變化重建。正確排序的命令可以提高 Docker 重建鏡像的速度。
此外,每個文件系統(tǒng)層可以在多個鏡像之間共享。當需要利用同一個基礎 Docker 鏡像多個新鏡像時,這會非常有用。例如,上述 Dockerfile 的基礎鏡像為 node:12-alpine,只要將其保存在某個地方,其他 Docker 鏡像就可以共享了。
構(gòu)建好鏡像后,還需要命名、打標簽,然后上傳到鏡像存儲庫,然后就可以輕松與他人共享了。
雖然有了鏡像就可以構(gòu)建可重現(xiàn)的 Docker 容器,但 Docker 并不能保證創(chuàng)建的鏡像是可重現(xiàn)的,也就是說即便使用同一個 Dockerfile,運行兩次 docker build,也有可能得到兩個行為不同的鏡像。例如,第三方軟件包可能在不知不覺間發(fā)布了更新,因此導致構(gòu)建的鏡像也發(fā)生變化。確保使用固定的某個版本可以從一定程度上緩解這個問題,但也不能完全杜絕。
另一個需要注意的問題是,在創(chuàng)建鏡像時,Docker 只允許從單個文件系統(tǒng)層繼承。你可以在 Dockerfile 中指定基礎鏡像,但不能將兩個鏡像合并起來。假設你想使用 node 和 rustc 構(gòu)建容器,由于不能將 node 和 rustc 的鏡像合并起來,所以只能先利用 node 鏡像構(gòu)建容器,然后再手動安裝 rustc,或者反過來,先用 rustc 構(gòu)建容器,然后再手動安裝 node 。
使用 Nix 構(gòu)建可重現(xiàn)的環(huán)境
為了構(gòu)建可重現(xiàn)的環(huán)境,Nix 采用了最基本的方式。Nix 提供了一個完整的構(gòu)建系統(tǒng),可以單獨構(gòu)建各個軟件包。
在構(gòu)建軟件包時,你需要執(zhí)行如下步驟:
使用所需工具構(gòu)建環(huán)境。
執(zhí)行腳本,運行所有構(gòu)建命令。
每個軟件包的構(gòu)建都可以重復這個過程,為了確保每個環(huán)境都是可重現(xiàn)的,Nix 做了大量努力。首先,Nix 會限制網(wǎng)絡訪問、文件系統(tǒng)訪問,有時甚至還會在構(gòu)建過程中在沙盒容器中運行,以防止構(gòu)建過程中受到任何外部因素的影響。
由于軟件包會依賴第三方庫,形成巨大的依賴關系網(wǎng),因此 Nix 會在構(gòu)建過程中遍歷所有依賴項。
而你可以使用 Nix 創(chuàng)建可重現(xiàn)的環(huán)境,只需在關系網(wǎng)中添加一個節(jié)點,然后讓 Nix 完成第一步構(gòu)建環(huán)境的工作即可。
在開發(fā)中,你可能不希望或不需要 Nix 執(zhí)行第二步。相反,你可以將 Nix 放入bash shell,自行運行構(gòu)建命令。
Nix 自帶一個名叫 nix-shell 的工具,它可以為你完成這項工作。將 shell.nix 文件添加到項目的根目錄,nix-shell 就可以根據(jù)所有指定的軟件包構(gòu)建一個新環(huán)境。
下面是一個 shell.nix 文件的示例:
{ pkgs ? import <nixpkgs> {} }:
pkgs.mkShell {
nativeBuildInputs = [ pkgs.rustc pkgs.cargo ];
}
Nix 并不會使用容器,它只會修改環(huán)境變量。例如,將某個二進制包添加到環(huán)境變量 PATH 中。
由于 Nix 可以保證構(gòu)建可重復的環(huán)境,因此,只需共享此 shell.nix 文件,即可為開發(fā)人員提供功能完全相同的開發(fā)環(huán)境。
與 Docker 不同,Nix 是一個成熟的包管理器,因此 Nix 可以自由組合環(huán)境。以上述 Docker 示例為例,如果我們想在同一個環(huán)境中同時部署 node 和 rust,則只需告訴 Nix 將二者添加到構(gòu)建的輸入中:
{ pkgs ? import <nixpkgs> {} }:
pkgs.mkShell {
nativeBuildInputs = [ pkgs.rustc pkgs.cargo pkgs.nodejs-16_x ];
}
應該使用哪個?
在考慮這個問題之前,我們首先應該回顧一下:Docker 和 Nix 是完全不同的工具。
Docker 鏡像只是 Docker 提供的一小部分,Docker 為整個容器生態(tài)系統(tǒng)提供了工具。
而 Nix 的設計旨在構(gòu)建可重現(xiàn)的包和環(huán)境。
如果你的目的是構(gòu)建可重現(xiàn)的開發(fā)環(huán)境,則選用 Nix 更合適。
如果你希望尋找一種方法來構(gòu)建、打包和部署自己的服務,則 Docker 提供的更豐富的工具更適合你。畢竟,容器乃是如今部署 Web 服務的標準方式。
即便如此,Docker 鏡像的構(gòu)建仍有很多不足之處。不過,好在我還有一張王牌:利用 Nix 構(gòu)建Docker 鏡像例如:
{ pkgs ? import <nixpkgs> { }
, pkgsLinux ? import <nixpkgs> { system = 'x86_64-linux'; }
}:
pkgs.dockerTools.buildImage {
name = 'cowsay-container';
config = {
Cmd = [ '${pkgsLinux.cowsay}/bin/cowsay' 'I'm a container' ];
};
}
如此一來,我們就可以魚與熊掌兼得:
利用 Nix 構(gòu)建可重現(xiàn)的 Docker 鏡像。
利用容器簡化部署。
Replit 如何使用 Nix
目前,我們正在從通過巨大的 Docker 鏡像 Polygott 提供開發(fā)工具朝著通過 Nix 提供軟件包過渡。你可以看一看用戶使用Nix構(gòu)建的應用(https://replit.com/apps/nix)。
我們發(fā)現(xiàn), Nix 解決了我們在使用 Docker 鏡像時遇到的下列問題:
很難獲知兩種不同版本的 Polygott 之間發(fā)生了什么變化。
第三方庫的更新,可能會導致用戶的執(zhí)行環(huán)境出現(xiàn)各種問題。
第三方庫的下線,會導致鏡像構(gòu)建出問題。
很難將所有支持語言的開發(fā)工具組合到一個 Docker 鏡像中。
Polygott 提供了一堆腳本和配置文件,用于安裝每種語言所需的工具,這些維護工作非常痛苦。
我們做了很多嘗試,但最終發(fā)現(xiàn)沒有辦法在多個鏡像構(gòu)建之間共享一些文件系統(tǒng)層。Polygott 本身的大小為 20~30GB,如果在生產(chǎn)環(huán)境中安裝多個版本,規(guī)模會迅速膨脹。
如今在使用了 Nix 后,我們可以讓用戶自由選擇他們需要的工具。同時還解決了上述問題,減輕了我們內(nèi)部的維護負擔。雙贏!
雖然我們使用了 Nix,但我們也不會因此而放棄 Docker 容器。我們?nèi)匀恍枰萜鱽頌槊總€用戶執(zhí)行環(huán)境提供一個隔離的環(huán)境。
Nix 會超越 Docker 嗎?不會,這兩款工具實現(xiàn)了不同的目標,二者的結(jié)合可以提供完美的解決方案:可重現(xiàn)的環(huán)境與容器化的部署。
原文鏈接:https://blog.replit.com/nix-vs-docker
聲明:本文為 CSDN 翻譯,轉(zhuǎn)載請注明來源。
END