緣由
我開發 PHP,不管是純 PHP 或 Laravel 都很喜歡用 Homestead,他優點是可以起一台 VM 就可以同時使用多個專案,而且透過修改一個簡單的 Homestead.yml 就可以做完全部環境的設定,但官方已經宣布不維護了,身為資訊人,你要嘛 fork 一份自己維護,不然就要跟著時代走。
不維護的原因也很簡單,VM 真的很肥很吃資源,現在 docker 實在是太香了,而且 Windows 引入 WSL 以後,Docker Desktop 也是秒級啟動,Laravel Sail 也是 base on Docker Compose,是時候跟 Homestead 分手了 。°(°¯᷄◠¯᷅°)°。。
SMB
WSL 有一個痛點,你用他預設掛載進去的目錄執行任何程式碼都慢到哭,目前我實作比較好的方式是透過 SMB 把你的工作目錄掛進去,跑起來跟在本機執行相差無幾,首先是在你要分享的目錄上按右鍵選內容,共用 -> 進階共用 -> 共用此資料夾,共用名稱建議字尾加個 $,這樣在網路芳鄰上會看不到這個目錄,像是 code$,這樣我打 \\HOST\\code$ 可以連到,在公網上是看不到的,另外是我在本機多設定了一組 smb/smb 帳號,這樣我在 demo 的時候也不會因為被看到密碼尷尬,SMB 沒設定的話預設是你這台 Windows 的登入帳號密碼。
WSL
我並沒有使用 Docker Desktop,因為他開機即啟動,而且無法手動 terminate,WSL 資源雖然吃不多,但我沒有要開發的時候並不需要你來吃我這份資源,我知道網路上有許多改機碼之類的方式可以達成這個目的,但官方沒給的我認為都是邪門歪道,我很不喜歡用非官方的方式解決問題,所以我自己起一台 Ubuntu wsl --install -d Ubuntu --name develop,之後再進去自己安裝 Docker,自由自在~
進去 WSL 以後我建議一律 sudo su,用 root 可以少遇到很多奇奇怪怪的權限問題,再來就是手動掛載 smb 了,我將指令放到 ~/.bashrc,因為 WSL 沒有固定 IP,所以你沒辦法把掛載寫死,要的話也是要寫一堆 script 去判斷東西,我是覺得沒必要。
~/.bashrc
_get_windows_ip() {
local gateway_ip
gateway_ip=$(awk '$1 == "00000000" {print strtonum("0x"substr($3,7,2))"."strtonum("0x"substr($3,5,2))"."strtonum("0x"substr($3,3,2))"."strtonum("0x"substr($3,1,2))}' /proc/net/route | head -1)
if [[ -n "$gateway_ip" ]]; then
echo "$gateway_ip"
return 0
fi
gateway_ip=$(ip route show | awk '/^default via/ {print $3}' | head -1)
if [[ -n "$gateway_ip" ]]; then
echo "$gateway_ip"
return 0
fi
gateway_ip=$(awk '/^nameserver/ {print $2; exit}' /etc/resolv.conf)
if [[ -n "$gateway_ip" ]]; then
echo "$gateway_ip"
return 0
fi
echo "" >&2
return 1
}
_mount_smb() {
local smb_share="$1"
local mount_point="$2"
local smb_username="smb"
local smb_password="smb"
local smb_host
if [[ -z "$smb_share" ]] || [[ -z "$mount_point" ]]; then
echo "Usage: _mount_smb <share> <mount_point>" >&2
return 1
fi
if mountpoint -q "$mount_point"; then
echo "Already mounted at $mount_point"
return 0
fi
[[ -d "$mount_point" ]] || mkdir -p "$mount_point"
smb_host=$(_get_windows_ip)
if [[ -z "$smb_host" ]]; then
echo "Error: Cannot determine Windows host IP" >&2
return 1
fi
echo "Detected Windows host IP: $smb_host"
echo "Attempting to mount //$smb_host/$smb_share to $mount_point"
if ! timeout 3 bash -c "</dev/tcp/$smb_host/445" 2>/dev/null; then
echo "Error: Cannot connect to $smb_host:445 (SMB port)" >&2
return 1
fi
echo "SMB port is reachable, mounting..."
if sudo mount -t cifs "//${smb_host}/${smb_share}" "$mount_point" \
-o username="$smb_username",password="$smb_password",vers=3.0,uid=$(id -u),gid=$(id -g),file_mode=0777,dir_mode=0777 2>/dev/null; then
echo "✓ SMB share mounted at $mount_point"
return 0
else
echo "SMB 3.0 failed, trying SMB 2.1..."
if sudo mount -t cifs "//${smb_host}/${smb_share}" "$mount_point" \
-o username="$smb_username",password="$smb_password",vers=2.1,uid=$(id -u),gid=$(id -g),file_mode=0777,dir_mode=0777 2>/dev/null; then
echo "✓ SMB share mounted at $mount_point (SMB 2.1)"
return 0
fi
echo "✗ Failed to mount SMB share" >&2
return 1
fi
}
if [[ $- == *i* ]] && [[ $SHLVL -eq 1 ]]; then
_mount_smb "code$" ~/mnt/code
fi
這段程式碼簡單講就是把我在本機 share 的 \\HOST\\code$ 掛到 ~/mnt/code,需要多掛幾個多執行幾次 _mount_smb 即可,每次進入 WSL 便抓取 Host Gateway IP 來連結,目錄跟檔案權限都設定成 777 避免一堆怪事發生,反正開發機不怕。
實際測試
這邊獻上幾個環境跑 phpunit 測試結果,測試數量 1364:
Host
real 1m 37.34s
real 1m 37.83s
real 1m 36.08s
平均:1m 37.08s
Vagrant + SMB
real 3m58.906s
real 3m32.119s
real 3m17.965s
平均:3m 36.33s
WSL + SMB
real 1m48.497s
real 1m57.464s
real 1m50.013s
平均:1m51.991s
其實在 Vagrant 裡面即便使用 SMB 執行速度還是悲劇,我已經這樣操作很久了,但因為 Homestead 的便利性一直選擇忽視這個問題,反正也不是整天跑 full test,另外有趣的一件事情,在 WSL 裡面使用 docker build 一個 php 環境跑測試,我預計應該要掉個 3% 的速度,因為它是 Host -> WSL -> Docker Volume,多一層轉換理應要消耗吧,但沒想到跑了幾次的結果它居然跟在 Windows Host 跑的時間差不多,php.ini 除了調整 memory_limit = -1 以外都沒動,不確定是 Docker 預設拉取的 OS 是 Alpine Linux 效能很好,還是 PHP 官方的 container php.ini 的配置有優化,總之蠻意外的。
之後預計會使用 WSL + Docker + Laravel/Sail 開發測試專案啦,謝謝你 Homestead。