我終於完成了我的第一個音樂專案,stuffy knows。可以在 bandcamp 上聽到!🎵

這個部落格已經在 Digital Ocean 的 VPS 上運行了十年以上。這台位於紐約的機器運行著 Ubuntu 16.04 LTS,一個已經停止支援至少五年的 LTS 版本。是時候該換了。經過一番考慮,我遷移到了一台 Hetzner 的虛擬機器,這台機器比我舊的 Ubuntu 機器好得多,價格不到我過去支付的一半,而且就在我國家對面。不僅如此,我還接受了挑戰,將我的技術堆疊轉移到 FreeBSD。

這是一篇很長的文字,但請留下來,看看 FreeBSD Jails 搭配 Bastille 的精彩介紹,以及一些有趣的網站載入效能測試。

如果你知道 Ubuntu 的版本發布機制(我自己也不是很熟悉),一旦版本停止支援,apt 套件儲存庫就會失效,你將無法再從中取得任何更新。運行如此過時的系統有幾種影響,最明顯的是你的伺服器安全性將大打折扣。可能會有許多機器人試圖尋找有漏洞的節點,以便植入惡意程式。幸運的是(我認為),從未發生過什麼事。反正裡面也沒有什麼重要東西值得被竊取。但我記得很久以前,我的一個 WordPress 部落格,也是運行在一個舊 VPS 上,隨機出現了大量指向賭場和博弈網站的可疑連結,散佈在文章文字中。

我已經在使用 Hetzner 的 VPS 作為遠端開發機器,我從任何地方透過 SSH 連線進去,以這個價格來說,它一直是一個可靠的好 VPS。所以我決定從比較規格開始。這是我運行部落格和其他網站的 Droplet:

這台機器有 2GB RAM、一個 vCPU、50GB 硬碟、每月 2TB 的流量,運行 Ubuntu 16.04 x64。它位於紐約的資料中心。這大概就是為什麼它這麼貴,我每月支付 13 美元。

Hetzner 是一家知名的歐洲 IT 公司,在德國(我住的地方)設有大型資料中心。我能買到的最便宜的 Hetzner 伺服器,僅需 3.56 歐元,就已經比我舊的機器好多了:

記憶體和 CPU 翻倍,儲存空間略少,但流量是十倍。但我決定選擇一個更強大的配置,每月不到 6 歐元:

這對我的網站來說可能有點過度,但為什麼不呢?

我的舊設定除了這個部落格之外,還運行著一些其他網站。沒有什麼特別受歡迎的;這個部落格是所有網站中最受歡迎的,每月瀏覽量不會超過幾千次。除非有幾篇文章在 Hacker News 上爆紅,否則流量並不大。最終,這台機器基本上只是提供靜態網站,沒有複雜的 CGI 或自訂程式碼運行。技術堆疊很簡單。一切都透過 nginx/1.10.3 靜態提供。所以我基本上會為每個網站準備幾個位於 /etc/nginx/sites-available 的設定檔。額外必要的程式,如靜態網站產生器和 LaTeX 套件(例如:這個部落格是由 Hugo 產生的),都是透過 apt 或 snap 安裝的。事實上,我更新部落格的流程是:

在 VPS 的早期,我也用它來運行一些測試和做一些程式設計。所以它塞滿了很多我不再使用的過時軟體。但它就是能用。而且用得相當好。

好到什麼程度呢?在我關閉它時,它的正常運行時間是 1491 天!大約是四年不間斷運行!

我不得不說,我的主要動機之一是想接觸一些不同的東西。我一直在閱讀和觀看許多關於 BSD 的資訊,並且我之前對 FreeBSD 有過短暫的經驗,所以我認為這是一個將其投入實際測試的好方法。FreeBSD 通常因其穩定性而受到讚揚,這歸功於其整合式設計、安全性以及 Jails。

我不想聽起來像是知道自己在做什麼,但當我讀到 Jails 時,我就知道我確切想做什麼。Jails 是 FreeBSD 中一種形式的虛擬化/容器化技術,已經存在了 25 年以上,遠在 Docker 出現之前。乍看之下,它能做你對 Docker 容器的預期:它在內部沙盒化一個「迷你系統」,讓你可以在其中運行無法存取主系統的程式。我認為主要區別在於,Docker 和其他容器解決方案更適合「打包程式」。它們是短暫且不可變的。它們會操作來自容器外部的數據,但在容器內部,一切都將保持不變。而 Jails 實際上是子系統,幾乎就像迷你 VM,但實際上共享同一個核心。

此外,它的檔案系統 ZFS,對於伺服器來說實際上非常優秀且有用。如果你來自 Linux 世界,你可能聽說過 Btrfs,這是越來越多發行版最近採用的較新檔案系統。它與 ZFS 有些相似之處:數據完整性和快照。只是 ZFS 可能比 Btrfs 成熟得多。如果我經常拍攝系統快照,我就不需要依賴 VPS 提供商的快照或備份系統,這需要額外付費。

我的想法是為我的每個網站建立一個 Jail,其中包含建置它們所需的任何工具(例如,用於部落格的 Hugo),以及一個 nginx 實例來提供服務。然後有一個主要的 Web 伺服器 Jail,透過反向代理將所有這些網站連接到外部世界。這樣,如果其中一個 Jail 被入侵,我可以將其銷毀並建立一個新的。我將更詳細地介紹如何設定它們。

這裡有更多關於它的細節,給所有喜歡細節的人:

我不知道為什麼 fastfetch 總是報告比實際值更多的記憶體使用量。我在 btop 上從未見過這個伺服器使用超過 3GiB 的記憶體。

我將快速介紹一下我如何設定這個伺服器以及最重要的幾個點。我通常會保留開發日誌,所以在進行時我一直在記錄。然而,我不會將此視為一個指南,因為我可能遺漏了一些步驟。這更多的是關於在 Hetzner 上使用 FreeBSD 設定 Web 伺服器的一種體驗。

Hetzner 在建立 VM 時提供了一些映像檔,但它們相當有限:

但我看到一個指南,解釋如何在 Hetzner 上從官方 FreeBSD YouTube 頻道安裝。我非常推薦這個頻道。

問題是 Hetzner 實際上提供了 FreeBSD 映像檔,但它只是隱藏起來,需要幾個步驟才能找到。它提供的是 ISO 映像檔。所以,當被要求在建立過程中選擇作業系統映像檔時,你只需要選擇任何一個,然後你最終會將其全部清除。建立 VM 後,你只需要到主控台的 ISO Images 選項卡,然後掛載你想要的映像檔:

我選擇了 14.3。然後重新啟動。接著我基本上按照安裝程式進行。我不記得我是否更改了任何標準選項,但我當時是按照官方 FreeBSD 頻道的安裝影片進行的。瞬間,我就安裝並運行了系統。

我已經提到了幾次 Jails。但我不僅使用 Jails,還使用 Bastille,一個幫助管理 Jails 的系統。問題是手動建立 Jails 比我想要的要複雜一些。有幾個不同的步驟需要單獨處理,並且每次建立新 Jail 時都需要處理。Bastille 簡化了這一點,並提供了一切你需要的,只需一個 `bastille` 指令。例如 `bastille list` 顯示系統中的所有 Jail,`bastille create` 建立一個新的,`bastille console` 打開 Jail 的 shell 等。

安裝和啟用它(幾乎)和以下一樣簡單:

還有其他 Jail 管理器,我只是選擇了名字最酷的那個。

整個想法是讓一個 Jail 運行 Caddy 來服務所有網站並處理網域和 SSL 憑證。然後每個網站將有自己的 Jail,包含建置和服務它們所需的一切。伺服器 Jail 將透過反向代理將所有流量轉發到相應的 Jail。所以第一件需要做的事情是設定一個內部虛擬網路介面。我們可以將這個堆疊想像成網路中的一組虛擬機器。

要設定虛擬網路介面:

它基本上是複製一個迴環介面,命名為「bastille0」,並為其分配網路參數。Jail 將僅在此網路介面上工作。Caddy Jail 需要存取外部世界,因為它將是監聽請求的那個。為此,我們需要使用 PF(Packet Filter),FreeBSD 的防火牆,建立一些網際網路存取規則。

正如設定檔中所述,它基本上是設定我們 bastille0 和 tailscale1 網路上的所有內部流量;然後設定我們 Jail 和主系統的出站流量;然後將所有 80 或 443(HTTP 和 HTTPS)流量重新導向到內部 IP 10.0.0.5,這將是我們的 Caddy 伺服器。

這就是網路堆疊的全部內容。讓我們建立 Caddy 伺服器 Jail。

我的舊伺服器運行著老牌的 nginx。除了少量的 Apache,我大部分時間都運行 nginx。但 nginx 有一個令人討厭的問題,Caddy 可以快速解決:SSL 憑證。在我舊的伺服器上,我必須不時運行 certbot 來續訂網域 SSL 憑證,而且我錯過的續訂次數比我希望的要多。Caddy 會自動處理這個問題。

要建立一個新的 Jail,我們首先需要使用我們將使用的 FreeBSD 版本進行引導,在我的情況下是 14.3-RELEASE:

我們建立一個名為 `caddy` 的 Jail,使用 14.3-RELEASE,IP 為 10.0.0.5,位於 bastille0 介面上。

好了,它已經在運行了!請記住,Jail 不像 Docker 容器那樣是短暫的,你設定好容器的整個配置,然後啟動它,並期望機器永遠保持不變。我們在這裡幾乎是在處理虛擬機器。我們可以隨時透過以下方式存取 shell:

由於我們已經在 caddy Jail 中,我們只需要安裝 Caddy:

Caddy 的所有配置將在 Jail 中的 `/usr/local/etc/caddy/Caddyfile`。如果我們想從主系統存取該設定檔,我們需要將主系統的一個目錄掛載到 Jail 中。我們甚至可以以唯讀模式進行,這樣 Jail 就無法更改配置。類似這樣:

好的,伺服器幾乎都設定好了,但我們還沒有網站。所以讓我們建立一個網站 Jail,然後我們再回去設定 Caddy。

在疫情期間,巴西總統決定假裝 Covid-19 只是普通感冒,沒有採取任何緊急封鎖政策。他不斷說國家不會停止,因為負擔不起,如果有人必須死,他們就會死。完全無視科學甚至常識。歷史證明他是多麼錯誤。對此不滿,我想要「抗議」。

所以我創建了一個小頁面 es.cro.to,字面意思是葡萄牙語的「陰囊」,但經常被用來形容一個卑鄙小人、混蛋或類似的人。我購買了網域 cro.to(回想起來,這真的很酷,在英語中聽起來也很不錯),並添加了一個子網域 es。它像字典裡一樣,被分成單字的音節,所以我同時添加了單字的定義和一個生物的圖片:

當然,它是由我舊的伺服器託管的,並且在封鎖期間獲得了相當大的流量。我決定從它開始,畢竟它只是一個帶有嚴重壓縮照片的單頁面,而且現在已經不太相關了。

該網站是一個 git 儲存庫,就像我將要運行的所有其他網站一樣。所以我準備將所有網站放在主系統的 `/usr/local/www` 中,然後將特定的網站目錄掛載到各自的 Jail 中,以唯讀方式。所以一旦我將這個網站放在 `/usr/local/www/escroto` 中,我就用 bastille 建立 Jail。這次,我將使用一個用於 nginx 的 bastille 模板:`www/nginx`。模板只是一些預先安裝了一些軟體的小腳本。我本來不必這樣做,但為什麼不呢?

我使用 IP 10.0.0.11 建立的。我們一會兒就需要它。此時,該 Jail 的 nginx 已經在提供 nginx 的預設頁面。它位於 `/usr/local/www/nginx`(請注意,FreeBSD 將幾乎所有東西都放在 `/usr/local` 中)。

然後我用以下方式將主系統的網站目錄掛載到 Jail 中:

現在,從 Jail 內部,我可以在 `/usr/local/www/escroto` 中存取網站。由於它是一個 git 儲存庫,它包含不應該被服務的檔案(.git)。所以我編寫了一個 `deploy.sh` 腳本,將 `/usr/local/www/escroto/*` 的內容複製到 `/usr/local/www/nginx/*` 並刪除 `.git` 目錄:

我最終也在我那裡的其他網站上添加了 `/root/deploy.sh` 腳本。

非常簡單的配置。服務主網域以重新導向到 es.cro.to,然後反向代理到 IP 10.0.0.11,即 escroto Jail。

配置好 DNS 記錄後,網站就上線了。

這個部落格使用 Hugo,並且是 GitHub 上的 git 儲存庫。所以我將它克隆到 `/usr/local/www/blog` 並建立了一個新的 Jail,就像 escroto 一樣:

然後我在 Jail 中安裝了 Hugo:

部署腳本也差不多:

在我從舊伺服器遷移之前,我想做一些基準測試,所以我將部落格分配給了我的舊網域:

就這麼簡單,我的部落格已經在新伺服器上運行了。

在我更新 DNS 記錄之前,我想檢查一下新伺服器是否能承受流量。我的意思是,理論上是可以的,但我不知道我是否弄錯了哪個步驟。所以我開始尋找方法來測試我的網站 crocidb.com(指向舊伺服器)與 crocidb.cro.to。

首先,我開始思考我想測試什麼。測試延遲是沒有用的,因為我知道大多數訪問我部落格的人的延遲會稍微長一些。通常這個網站的大部分流量來自北美,其次是歐洲,然後是南美。但這真的不應該影響體驗。所以最好測試服務速度和高負載。

有一些免費的線上工具可以進行一般性測試,例如 GTMetrix、Pingdom 和 WebPageTest。但老實說,兩台伺服器之間的差異主要僅在於延遲。其他一切都差不多。所以我不得不深入研究。

我發現了 wrk 和 hey。兩種不同的工具用於測試 HTTP 負載。它們基本上會產生數千個並發請求到網站,並收集延遲、錯誤響應、每秒傳輸量等資訊。例如,我在 Hetzner 的另一個 VPS 上運行了 wrk:

這讓 wrk 在 4 個執行緒中運行,保持 100 個並發請求 30 秒。這不是一個不切實際的數字。結果是:

舊伺服器每秒處理 833 個請求,而新伺服器處理 12,260 個請求。平均延遲為 89 毫秒對 6 毫秒。但當然這不公平:測試機器與我的新伺服器位於同一個資料中心。所以我想到……如果我使用 VPN 並從多個地點進行測試呢?

我使用 Proton VPN,所以我編寫了一個快速腳本,可以從列表中獲取一個地點,連接,然後運行 wrk。結果非常令人失望。使用這些地點,它記錄了舊伺服器平均每秒 300 個請求,新伺服器每秒 800 個請求:

所以我放棄了使用終端用戶 VPN 的想法。我決定採取一個更強大的解決方案:在全球資料中心建立真正的 VPS。

我需要一個與託管我伺服器的 VPS 提供商不同的 VPS 提供商,以避免任何基礎設施優勢。我曾經使用過的除了 DigitalOcean 和 Hetzner 之外,另一個 VPS 提供商是 Vultr。然後我決定在每個地點建立一個伺服器並運行測試。我最終只選擇了四個不同的區域,因為這個過程是手動的:倫敦、聖保羅、矽谷和東京。所以我建立了這些地點中最便宜的四個 Fedora VM。

經過多次測試,我發現 hey 更符合我的需求。所以我的工作基本上是 SSH 到伺服器,運行這個腳本,然後複製結果。我從聖保羅開始:

它下載 hey,並以這些不切實際的重度數字運行:總共 100 萬個請求,10k 個並發請求,10 秒超時(如果任何特定請求花費超過 10 秒,hey 就會停止),總計 5 分鐘。

在第一次嘗試時,我就發現我的新 FreeBSD 伺服器存在一個真正的問題。它很早就失敗了,因為它無法處理 10k 個並發連接。經過大量研究,我發現你可以使用 `netstat -Lan` 檢查 socket 佇列的大小,它都是 128。結果發現 `kern.ipc.somaxconn` 的預設值就是這個數字。所以我增加了它:

大約運行 10 分鐘後,我的日誌就準備好了,我繼續查看:

聖保羅 VPS 上兩台伺服器 hey 輸出的比較

左邊是舊伺服器,右邊是新伺服器。差異令人難以置信。

雖然兩台伺服器都返回了大量的錯誤,但 FreeBSD 成功處理了預期的 100 萬個請求,而 Ubuntu 的請求數不到 2 萬個!這是巨大的差異。

cocidb.com 運行在舊伺服器上,而 crocidb.cro.to 運行在新伺服器上。

舊伺服器只能完成少量請求的事實令人難以置信。事實上,它只完成了大約 7% 的請求,而 FreeBSD 伺服器完成了 94%。新伺服器在東京的成功率略有下降,但我認為這沒什麼好擔心的。

考慮到每秒請求數,新伺服器至少是舊伺服器的 3 倍,最多是 11 倍!

每秒請求數:越高越好

這裡的差異看起來更為戲劇化,我猜測東京的延遲要大得多?

延遲百分位:p90 特別有趣,因為它衡量 90% 的用戶將體驗到的延遲低於該值。

從延遲百分位(例如:p50 表示 50% 的請求響應速度快於該值)來看,兩台伺服器的形狀有趣地不同。新伺服器直到大約 90% 才呈現更線性的增長,使其更可預測,而舊伺服器的增長則更不可預測。

這表明,即使在高需求下,全球 90% 的用戶嘗試加載我部落格主頁時,也能在不到 3.5 秒的時間內獲得內容。這相當不錯。

我沒有深入研究以理解東京的問題,但目前我不太擔心。使用請求階段細分,hey 分析請求的每個階段的持續時間,這表明到日本的流量較慢:

請求時間的階段細分

但這些數據對我來說看起來很奇怪。首先,DNS 撥號和查找在第二個網域上非常低。也許是因為它是 CNAME 記錄?其次,響應等待(基本上是首次響應時間)和響應讀取(傳輸時間)在這裡異常高。但這可以解釋為它只計算成功的請求,這可能表明第一台伺服器一開始很快,直到它基本上阻止了任何新請求。

我不認為這種差異與我選擇的技術堆疊有關。這很可能是由於舊 Ubuntu 系統的配置錯誤,以及我選擇的 Hetzner VPS 有 4 個 CPU 核心,而 DigitalOcean 的只有一個:可以同時處理更多的請求。我也不認為這有什麼意義,因為這些伺服器實際上不太可能同時需要處理這麼多請求。也許像 WebPageTest 這樣的單次網頁測試就足夠了。

儘管基準測試留下了許多未解答的問題,但我對此非常滿意。所以我繼續更新了 DNS 記錄。這現在正式運行在那台機器上。

總之,經過許多小時的實驗、調整、建置和破壞,我發現設定一個 FreeBSD 網站託管機器並沒有那麼複雜。有許多網路託管服務符合我的限制,我本來可以選擇使用它們。或者我可以安裝 Proxmox 來處理我的容器並透過視覺化儀表板管理我的系統。甚至 Sylve,FreeBSD 的對應軟體。但我喜歡我選擇的道路,因為我從中學到了很多。

最終,這一切都沒什麼意義,因為我的大部分流量都來自爬取它的 AI 系統……

標籤:sysadmin, devops, freebsd, linux