Windows Subsystem for Linux 環境配置 (最新 1709 版)

三年多前因為對於 Unix-like 及 terminal 環境的倚賴,我從 Windows 跳槽到了 Mac 陣營。而到了去年,微軟竟然在 Build 大會上拋出了震撼彈,推出了 Bash on Windows,讓 Windows 10 可以直接執行 user-mode 的 Linux binary,這也讓我開始思考是否會有跳槽回 Windows 的一天。這個專案終於在 2017/10 推送的 Windows 10 Fall Creators Update(又稱 1709 版)脫離 Beta 階段,並且正名為「Windows Subsystem for Linux」。與最初的版本相比,在穩定度及相容性上都有相當大的提升。本篇文章將簡單記錄 WSL 的環境配置,以及一些需要注意的事項。

WSL 原理

這邊借用了 Windows for Linux Nerds 一文中的 WSL 架構圖,基本上這些 ELF binary 是運行在一個個 Pico Process 環境中,與 cygwin 不同的是,這些程式所發起的 Linux system call 會直接打進 Windows NT Kernel,並且由 lxcore.syslxss.sys 這兩個 kernel component 所處理,並且轉換對應為 Windows NT 的 system call。因為這些轉換對應是在 kernel-mode 進行,所以 WSL 會有比 cygwin 更好的效能。

關於 WSL 原理的細節將不在本篇做更深入介紹,有興趣的可以看這張圖的原文,或是微軟官方也有一系列的部落格文章介紹一些內部細節。

啟用 WSL

在最新版本的 Windows 10 中,啟用 WSL 不再需要開啟開發者模式,只需要直接從控制台中啟用「適用於 Linux 的 Windows 子系統」這項功能即可。在 Windows 10 上可以透過打開 [設定],點選 [App] > 右側的 [程式和功能] > [開啟或關閉 Windows 功能] 找到這個選項。

如果懶得使用 GUI 的話,也可以透過 PowerShell 以系統管理員身份下指令:

Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Windows-Subsystem-Linux

安裝 distro

在最新版本中,Linux distro 已經不再僅限於 Ubuntu,這也是為什麼官方最終拋棄了「Bash on Ubuntu on Windows」這個名稱。另外,Linux distro 的安裝也被移到了 Windows Store 市集,截止目前已經有 Ubuntu、openSUSE、SUSE Linux Enterprise Server 三種選擇,Fedora 也已經在路上,不過暫無明確的時間表。

在市集點選安裝只算完成了第一步,接下來還必須從 [開始] 中打開對應的 distro(以下將以 Ubuntu 為例),並等待數分鐘的初始化,接著設定好帳號、密碼即可完成,並且會自動進入 Ubuntu 環境。

若跟我一樣習慣使用 zsh,可以參考以下的指令進行安裝,並且將 zsh 設定為 default shell。

$ sudo apt-get update
$ sudo apt-get install zsh
$ which zsh
/usr/bin/zsh
$ chsh -s /usr/bin/zsh

另外,也推薦一併安裝 Oh My Zsh 以及幾個方便的套件 (zsh-completionszsh-autosuggestionszsh-syntax-highlighting) 來達到類似 fish shell 的命令列功能。

$ sh -c "$(curl -fsSL https://raw.github.com/robbyrussell/oh-my-zsh/master/tools/install.sh)"
$ git clone https://github.com/zsh-users/zsh-completions ~/.oh-my-zsh/custom/plugins/zsh-completions
$ git clone git://github.com/zsh-users/zsh-autosuggestions $ZSH_CUSTOM/plugins/zsh-autosuggestions
$ git clone https://github.com/zsh-users/zsh-syntax-highlighting.git ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-syntax-highlighting

最後,使用 vim 編輯 .zshrc 以啟用 plugin。同時,也建議更換 zsh 佈景主題,因為某些主題疑似因為 Powerline 字體所以會讓 Console Emulator 沒辦法正常顯示游標位置,至於有哪些主題是沒有問題的可以自行嘗試,這邊將以 bira 為例。

ZSH_THEME="bira"
......
plugins=(… zsh-completions zsh-autosuggestions zsh-syntax-highlighting)

重新開啟 shell 後,此時我們可以透過 lsb_release -a 或是 screenfetch(需另外安裝,可參考這篇文章)驗明正身,確認這真的是一個 Ubuntu Linux 環境。

另外值得一提而且神奇的是,WSL 環境下還可以直接執行 Windows 程式,甚至可以透過 pipe 做溝通。例如以下的指令會將 Linux Kernel 的版本資訊複製到 Windows 環境的剪貼簿(即 clip.exe):

$ uname -a | clip.exe

安裝 Cmder

基本上到此之後的步驟我就會放棄 Windows 內建的命令列介面,因為實在太……(下略)。在測試過了數個 Console Emulator 之後,我最推薦使用 Cmder,從官網下載 mini 版本即可。另外值得注意的是,建議將 Cmder 解壓縮到 C:\Program Files 以外的資料夾(例如 C:\Cmder)。主要是可能會遇到一些權限的問題,而若強制用系統管理員身份執行,在啟動 Linux instance 時會是跟一般權限不同的個體,可能會造成一些錯亂。

打開 Cmder 後首先進行一些基本設定,包含字型與顏色配置。在這邊我所選用的是 Meslo LG M Regular for Powerline 字型以及 Solarized Git 配色。由於 zsh 的許多佈景主題可能都會用到 Powerline 字型,所以原則上會建議優先考慮有打過 Powerline patch 的字型。

接著,切換到 [Startup] > [Tasks] 頁面,新增一個 ubuntu 的 Task,並且設定預設啟動的指令。其中,-cur_console:p 參數是為了讓方向鍵能正常運作。

ubuntu.exe -cur_console:p

若希望 Cmder 開啟時能直接預設進入到 Ubuntu 環境,則可在 [Startup] 頁面中進行選擇。

至此,已經完成了還算美觀的 shell 及 Console Emulator 設定。

設定 distro

在最新版本的 Windows 10 中,由於 WSL 支援多種 distro,並且這些 distro 是可以同時執行的,所以也多提供了一個工具 wslconfig.exe 可以查詢目前可用的 distro 以及設定預設的 distro。以下示範使用 wslconfig.exe /list 指令列出目前系統上所有安裝的 Linux distro。若需要修改預設設定,可以使用 /setdefault 參數。

為了支援多種 distro,有別於過去的版本只有一個 bash.exe 存在系統中,最新版本的 WSL 又做了一些改變。首先,透過 where 指令(類似 Linux 中的 which)可以看到以下這些執行檔的存放位置。其中,因為目前 Linux distro 是以 Windows App 的型式透過市集發佈的,所以 Ubuntu 與 openSUSE 會各自有一個執行檔存放在 WindowsApps資料夾底下,也是它們各自的進入點。

而這幾個執行檔又有以下的差別:

  • bash.exe: 進入預設 Linux distro 的 bash shell
  • wsl.exe: 進入預設 Linux distro 的 default shell
  • ubuntu.exe: 進入 Ubuntu 的 default shell
  • opensuse-42.exe: 進入 openSUSE 的 default shell

在 Ubuntu 執行的同時,於新的命令列介面執行 opensuse-42.exe 便可同時運行一個 openSUSE 環境。

OpenSSH Server 設定

Ubuntu 環境本身已經預載了 OpenSSH Server,我們只需進行一些設定便可從外部 SSH 連線進去。首先,先使用 ssh-keygen 產生 key,接著打開 sshd_config 來編輯設定:

$ sudo /usr/bin/ssh-keygen -A
$ sudo vim /etc/ssh/sshd_config

主要有兩個設定需要修改,分別是監聽的連接埠以及允許使用密碼認證的選項。由於 Windows 10 自從某一版開始內建了 SSH server,所以 port 22 可能是被系統佔用的,這裡我們可以修改成 2222 以避免衝突。另外,因為無論是記憶體或是網路操作終究是由 Windows NT Kernel 所管理,所以若要允許外部連線也需要在 Windows 防火牆中設定連接埠規則。

Port 2222
......
PasswordAuthentication yes

最後只需啟動 OpenSSH Server 即可。

$ sudo /etc/init.d/ssh start

Linux Instance 生命週期

這邊我們使用 apt-get 在 WSL 的 Ubuntu 環境底下安裝 nginx web server:

$ sudo apt-get install nginx

一般在 Ubuntu 16.04 底下,我們會使用 systemd 來管理服務,但由於 WSL 的 Ubuntu 有自己的 init system,我們是無法使用 systemctl 的,所以我們需要使用比較傳統的方式來啟動 nginx:

$ sudo service nginx start
or
$ sudo /etc/init.d/nginx start

啟動之後我們可以在 Windows 底下的瀏覽器開啟 http://localhost 確認 nginx 是否有成功跑起來,這邊也可以注意到 WSL 環境是跟 Windows 環境共用 port 的,不需要額外做 port forwarding。此時使用 tophtop 也可以看到 nginx 的 process,並且在 WSL 環境底下看不到 Windows 的 process。反之,在 Windows 的工作管理員則可以看到這些 WSL 底下的 process。

此時,如果我們將所有 WSL 的 terminal 關閉,會發現就沒辦法正常 access 到網頁了。重新打開 WSL 後也可以觀察到 uptime 已經被歸零重計,也不見 nginx 的 process。

從這裡我們便可瞭解到 WSL 環境並非會一直存在於背景,它的生命週期會延續到最後一個 session 被關閉為止。另外,WSL 目前也還沒有支援 cron 排程。

VS Code 設定

由於 WSL 可以直接執行 Windows 程式,我們可以直接在指定資料夾使用 code . 指令開啟 VS Code。不過這邊有點怪異的是,在 VS Code 視窗打開之後,指令並沒有被返回,這裡可能是 WSL 的 bug,按下 Ctrl-C 即可解決。另外需要注意的是,無論如何請勿在 Windows 環境碰觸 WSL 的檔案系統,也就是說專案檔案請放在 Windows 的 C 或 D 碟,而它們會分別被掛載在 WSL 底下的 /mnt/c/mnt/d,這也是為什麼 WSL 的 rootfs 並沒有被放在一個容易找到的地方。

VS Code 中的 integrated terminal 也可以設定為開啟 WSL 的 shell,以下是一個範例設定。其中,bash.exe -c zsh 表示執行預設 Linux distro 底下的 bash 並且進入 zsh 環境,當然直接使用 ubuntu.exe 也是沒有問題的。

{
"editor.fontFamily": "Fira Code",
"editor.fontSize": 16,
"editor.fontLigatures": true,
"editor.rulers": [
79
],
"terminal.integrated.shell.windows": "C:\\Windows\\Sysnative\\bash.exe",
"terminal.integrated.shellArgs.windows": [
"-c",
"zsh"
],
"terminal.integrated.fontFamily": "Meslo LG M for Powerline",
"terminal.integrated.fontSize": 16
}

Vagrant 設定

Vagrant 是我相當常用的虛擬機器管理工具,透過它除了可以用簡單幾行指令建立虛擬機器,也可以撰寫 Vagrantfile 設定檔快速建立出可重現的虛擬機器環境。基本上 Vagrant 可以視為 hypervisor 的前端,它的 backend(在 Vagrant 的世界稱為 provider)可能是 VirtualBox、VMware 或是 Hyper-V。因為這些 hypervisor 本身都需要 kernel 的虛擬化支援,而 WSL 本身並沒有真正的 Linux Kernel,所以單靠 WSL 是沒辦法達成的。

Vagrant 也算是比較重視 WSL 的工具,除了本身有針對 WSL 做一些特殊處理外,在官網上也有獨立的說明文件,不過其實寫得有點 confusing 就是。總結來說,雖然目前 WSL 已經可以直接執行 Windows 底下的程式,但直接執行 vagrant.exe 可能會遭遇一些問題,所以(至少)目前必須在 Windows 及 WSL 上安裝相同版本的 Vagrant,並且在 Windows 上準備好 VM provider(例如 VirtualBox)。Windows 上可以直接從官網下載 Vagrant 安裝檔,而 WSL 所需的 Debian package 下載連結也可以從同個頁面取得。

$ wget https://releases.hashicorp.com/vagrant/2.0.0/vagrant_2.0.0_x86_64.deb
$ sudo dpkg -i vagrant_2.0.0_x86_64.deb

此外,有幾個環境變數也需要在 WSL 底下做設定,才能正常在 WSL 環境底下使用 Vagrant。

$ export VAGRANT_WSL_ENABLE_WINDOWS_ACCESS="1"
$ export VAGRANT_WSL_WINDOWS_ACCESS_USER_HOME_PATH="/mnt/d"

由於 WSL 與 VirtualBox 的 Serial Port 溝通之間仍有一些問題,會造成 log 檔無法正常導出,所以 Vagrantfile 也需要做一點修改:

config.vm.provider "virtualbox" do |vb|
vb.customize [ "modifyvm", :id, "--uartmode1", "disconnected" ]
end

至此,便可使用 WSL 底下的 vagrant 指令(非 vagrant.exe)將 VM 跑起來並 SSH 進去。特別注意的是,我們是使用 Linux 版本的 Vagrant 控制 Windows 環境底下的 VBoxManage.exe 管理 VirtualBox 裡面的虛擬機器,並且是透過 Windows NT Kernel 來進行虛擬化。至於 Windows 版本的 Vagrant 到底做了啥,其實好像也沒用到,所以我也還有些不理解為何強制兩個環境都要安裝 Vagrant......XD

Docker 設定

Docker 的設定也還算容易,只需要記住一個概念:WSL 本身沒有 Linux Kernel,所以我們是沒辦法使用 Linux 版本的 Docker 來建立 container 的,因為 namespace 和 cgroups 等功能目前還沒有辦法被 Windows NT Kernel 所處理。

要在 WSL 裡使用 Docker,我們仍然需要 Windows 環境底下的 Docker daemon,無論是最新基於 Hyper-V 的 Docker for Windows 或是 Docker Toolbox 搭配 VirtualBox 所建立的 boot2docker 虛擬機器環境皆可。而 WSL 底下我們則是使用 Docker client 連線到 Windows 環境的 Docker host 進行操作,這只需要透過設定環境變數即可達成。

$ export DOCKER_HOST=tcp://127.0.0.1:2375
$ export DOCKER_TLS_VERIFY=1
$ export DOCKER_CERT_PATH=......

相關資源

Reddit 上可以找到比較集中的討論串,另外微軟也使用了 GitHub Issues 來管理公開回報的 issue,可以先上去搜尋可能會遇到的雷或回報問題。

目前微軟對於 WSL 的開發主要著重在 command line 使用的情境,但其實許多網友也已經測試過 WSL 是可以透過 X Server 執行 GUI 程式的,甚至還可以在 WSL 裡面用 Wine 跑 Windows 程式(什麼鬼…),不過我本身沒有這種需求所以還沒試過,有興趣的應該 Google 上也可以找到不少資源。