IIS 是一個相對穩定的產品,從 Windows Server 2016 推出 IIS 10.0 之後,版號就不再更動,而且每次作業系統釋出新版本也只會微幅新增功能,因此整體架構並沒有任何變化。本篇文章將分享我在管理 IIS 容器的一些心得與技巧。
建立容器
官方的 mcr.microsoft.com/dotnet/framework/aspnet:4.8
容器映象,已經預先安裝好 Web Server (IIS)
與 ASP.NET 4.8
角色服務,我將會使用這個 image 為主要的執行環境。
docker run --name=mysite -d --isolation=process -p 80:80 -p 443:443 -v C:\Projects\WebApplication1:C:\Inetpub\wwwroot mcr.microsoft.com/dotnet/framework/aspnet:4.8
以下對上述參數進行說明:
--name=mysite
設定容器好記名稱為 mysite
,方便後續命令說明。
-d
代表我們要將容器執行在 detach 模式(背景執行)。
--isolation=process
由於我們要複製現有的 PFX 憑證進容器,在 Windows 10 的 Windows Containers 必須要使用 proccess
隔離模式執行,才能執行 docker cp
命令複製檔案。
-p 80:80 -p 443:443
由於 Windows Containers 執行容器時,預設採用 nat
模式,所以需要將容器的 IP:Ports
對應到本機 Ports
比較方便測試。
-v C:\Projects\WebApplication1:C:\Inetpub\wwwroot
將一個現有的 ASP.NET MVC 5 專案對應到容器的 C:\Inetpub\wwwroot
路徑。
--entrypoint powershell
因為微軟官方的 aspnet
容器映象預設只能跑在 detach
(-d
) 模式,為了方便我們測試,我想改用 powershell
來當成預設進入程式。
mcr.microsoft.com/dotnet/framework/aspnet:4.8
已經預載 Web Server (IIS)
與 ASP.NET 4.8
角色服務。這是微軟官方的「多架構」容器映象,詳見 Windows Container 版本相容性與多重架構容器映像介紹 文章。
接著透過 docker exec
進入容器中操作:
docker exec -it -w c:\inetpub\wwwroot mysite powershell
我個人會特別將 PowerShell 加入一些快速鍵設定:
mkdir C:\Users\ContainerAdministrator\Documents\WindowsPowerShell
'# 設定按下 Ctrl+d 可以退出 PowerShell 執行環境
Set-PSReadlineKeyHandler -Chord ctrl+d -Function ViExit
# 設定按下 Ctrl+w 可以刪除一個單字
Set-PSReadlineKeyHandler -Chord ctrl+w -Function BackwardDeleteWord
# 設定按下 Ctrl+e 可以移動游標到最後面(End)
Set-PSReadlineKeyHandler -Chord ctrl+e -Function EndOfLine
# 設定按下 Ctrl+a 可以移動游標到最前面(Begin)
Set-PSReadlineKeyHandler -Chord ctrl+a -Function BeginningOfLine' | Out-File $PROFILE
. $PROFILE
複製現有 IIS 站台設定
我所認識的客戶與朋友中,大部分的人對 IIS 都是採用「設定飄移」(Configuration Drift)方法進行設定,也就是用「完全人工」的方式調整 IIS 中的任何設定。這種習慣會造成幾個問題:
- 隨心所欲的設定,其結果就是「忘記到底設定過些什麼」,然後在重新安裝時,無法「調」出跟原本站台完全相同的設定。
- 當你想建立容器時,由於容器中並沒有 UI 介面可以設定,需要大量仰賴 Windows PowerShell 來進行設定,對許多人來說是個門檻。
要將現有 IIS 站台的設定「複製」到另外一台,其實並不複雜,只要你能懂得一些基礎原理即可。
IIS 中所有的設定,其實是一種「階層結構」,整台主機有一個「根」設定,位於以下路徑:
C:\Windows\System32\inetsrv\config\applicationHost.config
然後站台實體目錄的根目錄下還有一個「本地」設定,如果實體目錄位於 c:\inetpub\wwwroot
的話,就是這個檔案:
c:\inetpub\wwwroot\web.config
但是你在 IIS 管理員中,透過 UI 所做的那些設定,有些會寫入「根設定」,有些則會寫入「本地設定」,你從 IIS 管理員那邊是完全看不出來的,必須開啟這兩個檔案,比對後才能知道到底改在哪裡。除非你很有經驗,否則肉眼無法判斷。
如果我們想把目前電腦上的 IIS 把所有站台移到另外一台主機,最簡單的方式是直接把「根設定」複製到新電腦的同名路徑下覆蓋即可!
不過,直接設定會有一些風險:
- 如果新電腦缺少安裝特定 IIS 模組,複製進去後 IIS 就無法啟動。
- 如果新電腦沒有預先安裝好 SSL 憑證,複製進去後,站台也無法正常瀏覽 HTTPS 安全網頁。
簡單歸簡單,上述方法應該可以說是最沒有門檻的網站搬移法。但是,請記得搬移完之後,要執行 iisreset /restart
重新啟動 IIS 服務!
將根設定複製進 IIS 容器的注意事項
將 C:\Windows\System32\inetsrv\config\applicationHost.config
設定檔複製進容器後,有很大的機率會讓你的 IIS 無法讀取的正確的設定,而且之後再透過 PowerShell 調整的 IIS 設定時,就算當下設定可以生效,但最終還是無法寫入到設定檔之中,導致容器重啟後設定依然沒有套用的情況。
此時在容器中執行 iisreset /restart
重新啟動 IIS 服務後,該容器就會自動停止!因為 IIS 就是我們當前容器的「主程序」,而容器的運作基本原理,就是當「主程序」停止時,容器就會自動停止!所以你要在容器停止後,重新啟動 IIS 容器即可:
docker start mysite
我一般來說,都會先將 applicationHost.config
設定檔複製出來,並且跟我目前 IIS 站台的 applicationHost.config
設定檔比對,然後將想加入的設定搬到容器的 applicationHost.config
裡面。以下是常用的 docker cp
命令:
-
從容器複製到本機
docker cp mysite:C:\Windows\System32\inetsrv\config\applicationHost.config g:\applicationHost.config
-
從本機複製到容器
docker cp g:\applicationHost.config mysite:C:\Windows\System32\inetsrv\config\applicationHost.config
直接複製 applicationHost.config
會引發的問題
這個問題是可以輕易重現的,假設我們的容器中站台設定如下:
PS C:\> Get-IISSite
Name ID State Physical Path Bindings
---- -- ----- ------------- --------
Default Web Site 1 Started %SystemDrive%\inetpub\wwwroot http *:80:
然而我本機的 IIS 設定如下:(多了一個 http *:81:
繫結)
PS C:\> Get-IISSite
Name ID State Physical Path Bindings
---- -- ----- ------------- --------
Default Web Site 1 Started %SystemDrive%\inetpub\wwwroot http *:80:
http *:81:
你只要將本機修改過的 applicationHost.config
複製到容器中,然後你就會發現,站台的新設定並沒有生效:
PS C:\> Get-IISSite
Name ID State Physical Path Bindings
---- -- ----- ------------- --------
Default Web Site 1 Started %SystemDrive%\inetpub\wwwroot http *:80:
但是用 Get-Website
又會看到新的設定:
PS C:\> Get-Website
Name ID State Physical Path Bindings
---- -- ----- ------------- --------
Default Web Site 1 Started %SystemDrive%\inetpub\wwwroot http *:80:
http *:81:
當你遇到這種鬼打牆的情況,就真的很難理解其中的道理!到底哪個才是對的?
由於 New-IISSite 來自於 IISAdministration 模組,而 New-Website 來自於 WebAdministration 模組。這兩個模組是這樣的:
簡單來說,你要在 Windows Container 中使用 PowerShell 管理 IIS 的話,使用 IISAdministration 就對了!
接著你直接執行以下命令建立站台:
New-IISSite -Name Test -PhysicalPath "C:\inetpub\wwwroot" -BindingInformation "*:80:localhost"
你會看到以下錯誤:
New-IISSite : Filename: \\?\C:\Windows\system32\inetsrv\config\applicationHost.config
Error: Cannot commit configuration changes because the file has changed on disk
At line:1 char:1
+ New-IISSite -BindingInformation ":80:localhost" -PhysicalPath "C:\ine ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [New-IISSite], FileLoadException
+ FullyQualifiedErrorId : System.IO.FileLoadException,Microsoft.IIS.Powershell.Commands.NewIISSiteCommand
重點在這裡:
Error: Cannot commit configuration changes because the file has changed on disk
然後你就會發現,剛剛的命令「感覺上」有設定成功,也就是新的站台設定「有生效」:
PS C:\> Get-IISSite
Name ID State Physical Path Bindings
---- -- ----- ------------- --------
Default Web Site 1 Started %SystemDrive%\inetpub\wwwroot http *:80:
Test 8712 C:\inetpub\wwwroot http *:80:localhost
如果你再執行一次,會遇到另一個錯誤:
New-IISSite : Web site 'Test' already exists.
At line:1 char:1
+ New-IISSite -Name Test -PhysicalPath "C:\inetpub\wwwroot" -BindingInf ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [New-IISSite], ArgumentException
+ FullyQualifiedErrorId : System.ArgumentException,Microsoft.IIS.Powershell.Commands.NewIISSiteCommand
這時你去執行 iisreset /restart
並重新啟動容器 docker start mysite
,再進入容器用 Get-IISSite
檢查,就會發現站台並沒有被新增進去:
PS C:\> Get-IISSite
Name ID State Physical Path Bindings
---- -- ----- ------------- --------
Default Web Site 1 Started %SystemDrive%\inetpub\wwwroot http *:80:
http *:81:
所以,請記得在複製 applicationHost.config
進容器後,立刻執行 iisreset /restart
並重新啟動容器!
相關連結