1. 程式人生 > >老司機實戰Windows Server Docker:2 docker化現有iis應用的正確姿勢

老司機實戰Windows Server Docker:2 docker化現有iis應用的正確姿勢

前言

上一篇老司機實戰Windows Server Docker:1 初體驗之各種填坑介紹了安裝docker服務過程中的一些小坑。這一篇,我們來填一些稍大一些的坑:如何docker化一個現有的iis應用。

問題分析

聽說Windows支援原生docker了,大家一定都很興奮。然而,大家想過沒有,Windows Server Docker最適合什麼場景呢?部署.NET Core應用?為什麼不選擇Linux下的docker?正常的決策者腦袋被門擠了才會花錢額外買Windows Server的license,用來部署.NET Core吧?所以,在本人看來,Windows Server Docker最大的價值,還是在於部署傳統基於WindowsServerCore的應用。這樣的應用一般有兩大類,一類是基於iis的網站應用;另一類是Windows Service。本文主要關注基於iis的應用的docker部署。

那麼,部署一個iis應用到docker,是不是隻要起一個iis的docker容器例項,遠端連線,並且,copy檔案進去,能通過容器內的iis訪問就行了?如果,有人問這樣的問題,那麼,說明他還完全沒有容器的思維。上面說的這個,其實就成了將容器當虛擬機器用了,這將極大地限制了docker原有的靈活擴充套件能力。因此,可以說是使用Windows docker最糟糕的姿勢之一了。

要正確部署一個iis應用到Windows Server Docker,並不是表面那麼簡單。限於篇幅,並且為了更專注,本文先不涉及容器編排、負載均衡、images的構建和管理等問題(這些要考慮的問題還有很多,以後我們單獨聊),這裡只關注如何將一個基於iis的應用正確運行於單個Windows Server Docker例項中。即便如此,一般至少也要解決下面這些問題:

  • Dockerfile:如何通過Dockerfile部署應用檔案和設定作業系統和IIS配置,如何為不同的執行環境(開發,測試,生產)配置不同引數;
  • 檢視系統日誌:典型的系統日誌包含IIS Logs、Windows Event Log和應用的異常日誌;
  • 重啟容器例項:當容器例項重啟時,如何保證被部署的應用能保持之前的工作狀態,能繼續服務;
  • 網路路由:包括容器內部如何訪問外部系統、docker宿主機如何訪問容器內部、外部系統如何訪問容器內部;

應用示例

這個應用只包含一個頁面,在我本機執行時,顯示類似下面的內容:

Hello Docker!

Configuration:
env1=Dev (from appSettings in web.config)
env2=Dev (from OS environment variable)

Content of C:\Windows\System32\drivers\etc\hosts:

# Localhost (DO NOT REMOVE)
127.0.0.1   localhost
::1 localhost ip6-localhost ip6-loopback

其中env1為web.config中的appSettings值,env2讀取的系統環境變數,頁面最下面打印出當前Windows系統的的hosts。

定義Dockerfile如下:

FROM microsoft/iis

# install ASP.NET 4.5
RUN dism /online /enable-feature /all /featurename:IIS-ASPNET45 /NoRestart

# enable windows eventlog
RUN powershell.exe -command Set-ItemProperty HKLM:\SYSTEM\CurrentControlSet\Control\WMI\Autologger\EventLog-Application Start 1

# set IIS log fields
RUN /windows/system32/inetsrv/appcmd.exe set config /section:system.applicationHost/sites /siteDefaults.logFile.logExtFileFlags:"Date, Time, ClientIP, UserName, SiteName, ServerIP, Method, UriStem, UriQuery, HttpStatus, Win32Status, TimeTaken, ServerPort, UserAgent, Referer, HttpSubStatus"  /commit:apphost

# deploy webapp
COPY publish /inetpub/wwwroot/iis-demo
RUN /windows/system32/inetsrv/appcmd.exe add app /site.name:"Default Web Site" /path:"/iis-demo" /physicalPath:"c:\inetpub\wwwroot\iis-demo"

# set entrypoint script
ADD SetHostsAndStartMonitoring.cmd \SetHostsAndStartMonitoring.cmd
ENTRYPOINT ["C:\\SetHostsAndStartMonitoring.cmd"]

# declare volumes
VOLUME ["c:/inetpub/logs/LogFiles"]

我們分別來理解一下Dockerfile每一段的含義:

  • 首先是安裝ASP.NET 4.5;
  • 接著,開啟Windows EventLog;
  • 第三步,我們修改了預設的IIS Logs欄位列表;
  • 第四步,將當前目錄下的publish目錄的內容複製到容器的/inetpub/wwwroot/iis-demo目錄,並且在iis中新增對應的iis-demo應用;
  • 第五步,設定一個自定義的啟動指令碼;
  • 最後,聲明瞭一個VOLUME,以便將IIS Logs儲存到容器外的宿主機上;

啟動指令碼SetHostsAndStartMonitoring.cmd的內容如下:

powershell -executionpolicy bypass -Command "If ($env:HOSTS) { $hosts = $env:HOSTS.Replace(':', ' ').Replace(',', '\r\n'); $hosts | Set-Content 'C:\Windows\System32\drivers\etc\hosts'; 'Applied hosts: ' + $hosts" }

powershell -executionpolicy bypass -Command "if ($env:env1) { (Get-Content 'c:\inetpub\wwwroot\iis-demo\web.config').replace('Dev', $env:env1) | Set-Content 'c:\inetpub\wwwroot\iis-demo\web.config' };

c:\ServiceMonitor.exe w3svc

其中,第一部分讀取HOSTS這個系統環境變數,覆蓋當前系統的hosts檔案;第二步讀取env1環境變數,覆蓋web.config中的對應配置;最後呼叫繼承自microsoft/iis image的ServiceMonitor.exe命令,監控iis主程序,直到其退出。

下面,我們來試著build這個docker。因為到目前為止(本系列的第一篇+第二篇),我們還只能從這臺Windows Server機器上執行docker命令,以後的文章會講到如何從遠端server build以及如何整合到CI工具進行build,這裡先繞過。我們在VS2015中編譯這個webapp,並且釋出到publish目錄。然後,複製整個windows-docker-iis-demo目錄到這臺docker宿主機的C盤根目錄,以便進行docker build。這個當然不是build docker image的正常姿勢,只是因為我們還沒提到其他方式,我們先粗糙一點,把它build出來,以便可以執行。

在我們的Windows Server 2016機器上,開啟一個administrator模式的powershell視窗,cd到c:\windows-docker-iis-demo目錄,然後執行docker build命令製作image:

docker build -t iis-demo:1.0 .

編譯成功後,執行docker images,可以看到多了一個iis-demo:1.0的docker image。接著,讓我們在宿主機的C盤建立一個temp目錄(為下面的volume使用,mount到容器內部的iis日誌),然後執行下面的命令執行一個iis-demo的docker instance:

docker run --ip 172.24.128.2 -p 80 -v "c:/temp:c:/inetpub/logs/LogFiles" -e "env1=LIVE1" -e "env2=LIVE2" -e "HOSTS=1.2.3.4:TEST.COM" iis-demo:1.0

這裡的引數分別表示:

  • 指定容器ip=172.24.128.2
  • 允許80埠被外部訪問
  • 將容器內的c:/inetpub/logs/LogFiles目錄mount到宿主機的c:/temp
  • 設定3個系統環境變數env1,env2,HOSTS

稍等片刻,等待容器例項執行,然後在宿主機的瀏覽器中訪問,可以看到如下的內容:

Hello Docker!

Configuration:
env1=LIVE1 (from appSettings in web.config)
env2=Dev (from OS environment variable)

Content of C:\Windows\System32\drivers\etc\hosts:
1.2.3.4 TEST.COM

對比前面在開發環境執行的結果,我們可以看到有一些有意思又詭異的區別:

  • 首先,通過前面的啟動指令碼SetHostsAndStartMonitoring.cmd讀取的環境變數env1和HOSTS都生效了;
  • 然而,在程式中執行時讀取的環境變數env2沒有生效(這個好坑人!!意味著,無法直接在webapp中讀取docker run傳遞進來的環境變數。一開始懷疑是因為webapp的程序啟動時間早於docker run指定的環境變數生效的時間,但是即使進到容器裡recycle 對應的apppool,還是不生效,具體原因有待後續驗證了);

另外,在宿主機的c:\temp目錄,我們可以看到從容器例項寫道外部的iis log。

好了,看看至此我們已經解決了哪些最開始提到的問題了:

  • 首先,我們實現了在docker run時,指定不同的引數,傳遞進容器,比如覆蓋web.config中的設定,又比如,設定了額外的hosts檔案中的dns解析;
  • 對於希望方便檢視的日誌,我們可以通過volume,mount到宿主機的目錄;
  • 同樣的,我們也可以mount應用自己的資料到宿主機,這樣容器例項重啟時,應用的狀態也能保持;
  • 因為可以在docker run時傳入引數被應用讀取,我們可以用同一個docker image,在不同的環境(開發、測試、生產)指定不同的引數,比如,資料庫連線字串;
  • 網路方面,關於如何從外部系統訪問容器內部,我們會在後續篇章詳細討論,這裡,因為可以將自定義hosts傳遞進容器,所以容器訪問外部系統的任何地址,都不用擔心無法解析;

應該說,我們已經解決了大多數前面提到但例項執行時需要解決的問題了。然而,別忘了,這一篇裡,我們只針對單伺服器,單容器例項。在實際的部署案例中,是絕不允許單點,無法擴充套件的。

後面幾篇,我會展開講講這一篇跳過的一些非常重要的話題,例如網路配置、遠端管理、負載均衡、實時監控、以及更高階的容器編排和叢集實現等等,敬請期待!