The Will Will Web

記載著 Will 在網路世界的學習心得與技術分享

如何在 Windows Containers 快速建立 IIS 站台與應用程式集區設定

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 中的任何設定。這種習慣會造成幾個問題:

  1. 隨心所欲的設定,其結果就是「忘記到底設定過些什麼」,然後在重新安裝時,無法「調」出跟原本站台完全相同的設定。
  2. 當你想建立容器時,由於容器中並沒有 UI 介面可以設定,需要大量仰賴 Windows PowerShell 來進行設定,對許多人來說是個門檻。

要將現有 IIS 站台的設定「複製」到另外一台,其實並不複雜,只要你能懂得一些基礎原理即可。

IIS 中所有的設定,其實是一種「階層結構」,整台主機有一個「根」設定,位於以下路徑:

C:\Windows\System32\inetsrv\config\applicationHost.config

然後站台實體目錄的根目錄下還有一個「本地」設定,如果實體目錄位於 c:\inetpub\wwwroot 的話,就是這個檔案:

c:\inetpub\wwwroot\web.config

但是你在 IIS 管理員中,透過 UI 所做的那些設定,有些會寫入「根設定」,有些則會寫入「本地設定」,你從 IIS 管理員那邊是完全看不出來的,必須開啟這兩個檔案,比對後才能知道到底改在哪裡。除非你很有經驗,否則肉眼無法判斷。

如果我們想把目前電腦上的 IIS 把所有站台移到另外一台主機,最簡單的方式是直接把「根設定」複製到新電腦的同名路徑下覆蓋即可!

不過,直接設定會有一些風險:

  1. 如果新電腦缺少安裝特定 IIS 模組,複製進去後 IIS 就無法啟動。
  2. 如果新電腦沒有預先安裝好 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

重點在這裡:

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

New-IISSite : Web site 'Test' already exists.

這時你去執行 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 並重新啟動容器!

相關連結

留言評論