使用 Docker 让传统 .NET 应用程序现代化
15 年來,Microsoft .NET Framework 一直都是成功的應用程序平臺,在舊版 Framework 和舊版 Windows Server 上運行的業務關鍵應用程序不計其數。這些傳統應用程序仍具有很大的業務價值,但其維護、升級、擴展和管理難度可能很大。同樣,沒有任何理由能證明投資完全重寫這些應用程序是合理的。借助在輕型容器中運行應用程序的平臺 Docker 和 Windows Server 2016,能夠賦予傳統應用程序全新的生命,不僅可以實現更多功能,還提升了安全性和性能,更是朝著持續部署這個方向邁出了重要的一步,而無需創建耗時長且成本高的重新生成項目。
在本文中,我將以連接 SQL Server 數據庫的整個 ASP.NET WebForms 應用程序為例,利用 Docker 平臺讓其現代化。我將先把整個應用程序原樣移動到 Docker 中,而不執行任何代碼更改,然后在輕型容器中運行網站和數據庫。接下來,我將介紹一種功能驅動型方法,用于擴展應用程序、提升性能并為用戶提供自助式分析。借助 Docker 平臺,你將了解如何迭代應用程序的新版本、安全快速地升級組件,以及如何向 Microsoft Azure 部署完整的解決方案。
Docker 如何在 .NET 解決方案中大展拳腳
Docker 適用于服務器應用程序,包括網站、API、消息傳送解決方案以及在后臺運行的其他組件。不能在 Docker 中運行桌面應用程序,因為 Docker 平臺和 Windows 主機之間沒有 UI 集成。因此,無法在容器中運行 Windows 窗體或 Windows Presentation Foundation (WPF) 應用程序(盡管可以使用 Docker 打包和分發這些桌面應用程序),但 Windows Communication Foundation (WCF)、.NET 控制臺應用程序和所有種類的 ASP.NET 應用程序都是合適之選。
若要打包應用程序以供在 Docker 中運行,需要編寫小型腳本文件 Dockerfile,用于自動執行所有應用程序部署步驟。這通常包括 Windows PowerShell 配置命令,以及用于復制應用程序內容和設置所有依賴項的指令。也可以解壓縮已壓縮的存檔或安裝 MSI,但打包進程全都是自動執行的,因此不能運行使用 Windows UI 并需要用戶輸入的安裝進程。
通過查看解決方案體系結構來確定哪些部分可以在 Docker 容器中運行時,請注意,不使用 Windows UI 即可進行安裝和運行的任何組件都是合適之選。本文將重點放在 .NET Framework 應用程序上,但你可以在 Windows 容器中運行 Windows Server 上運行的任何應用程序,包括 .NET Core、Java、Node.js 和 Go 應用程序。
將 .NET 應用程序遷移到容器中
如何遷移到 Docker 取決于應用程序的當前運行方式。如果是在 Hyper-V VM 中運行的完全配置應用程序,開放源代碼 Image2Docker 工具可以從 VM 的磁盤自動生成 Dockerfile。如果有用于發布 MSI 或 WebDeploy 包的生成進程,可以使用 Docker Hub 上的任一 Microsoft 基本映像編寫你自己的 Dockerfile。
下面展示了完整的 Dockerfile,用于編寫腳本將 ASP.NET WebForms 應用程序打包到 Docker 映像中:
FROM microsoft/aspnet:windowsservercore-10.0.14393.693 SHELL ["powershell"] RUN Remove-Website -Name 'Default Web Site'; \New-Item -Path 'C:\web-app' -Type Directory; \New-Website -Name 'web-app' -PhysicalPath 'C:\web-app' -Port 80 -Force EXPOSE 80 RUN Set-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Services\Dnscache\Parameters' \-Name ServerPriorityTimeLimit -Value 0 -Type DWord COPY ProductLaunch.Web /web-app九行腳本我全都需要,其并不涉及應用程序更改。假設有一個 ASP.NET 2.0 應用程序,當前在 Windows Server 2003 上運行。使用上面的 Dockerfile,我可以在映像中生成此應用程序,該映像可立即將此應用程序升級到 Windows Server 2016 和 .NET Framework 4.5。我將逐個介紹下面這些指令:
FROM microsoft/aspnet 指示 Docker 從哪個映像入手。在此示例中,從在 Windows Server Core 特定版本基礎之上安裝 IIS 和 ASP.NET 的 Microsoft 映像入手。
SHELL ["powershell"] 針對 Dockerfile 的剩余部分變為不同的 shell,以便我可以運行 PowerShell cmdlet。
RUN Remove-Website 使用 PowerShell 設置 IIS,同時刪除默認網站并為應用程序新建一個位置已知的網站。
EXPOSE 80 顯式公開端口 80,以允許網絡流量在 Docker 容器默認被鎖定時流入容器。
RUN Set-ItemProperty 禁用映像內的 Windows DNS 緩存,以便 Docker 能夠響應所有 DNS 請求。
COPY ProductLaunch.Web 將主機上 ProductLaunch.Web 目錄中已發布的網站項目復制到映像中。
Dockerfile 類似于 Web 應用程序的部署指南,但它不是含義模糊的用戶文檔,而是含義精確的可操作腳本。為了生成打包的應用程序,我從包含 Dockerfile 和已發布的網站的目錄運行 Docker 生成命令:
docker build --tag sixeyed/msdn-web-app:v1 .此命令生成名為 sixeyed/msdn-web-app 且標記為 v1 的 Docker 映像。此名稱包含我的 Hub 用戶帳戶名稱 (sixeyed),因此我可以使用自己的憑據登錄,并將這個映像發布到 Hub,從而共享它。標記可用于對映像進行版本控制,因此在打包應用程序的新版本時,映像名稱將保持不變,但標記會變成 v2。
我現在可以通過映像運行容器,這將會啟動應用程序,但示例應用程序依賴 SQL Server,因此我必須先運行 SQL Server,然后才能啟動網站。
從 Docker Hub 拉取依賴項
Docker 包含網絡堆棧。這樣一來,容器既可以通過虛擬網絡相互訪問,也可以訪問在物理網絡上運行的外部主機。如果 SQL Server 實例是在網絡中的一臺計算機上運行,那么容器中的 ASP.NET 應用程序可以使用它,我只需在連接字符串中指定服務器名稱即可。我也可以在容器中運行 SQL Server,Web 應用程序將能夠在連接字符串中指定容器名稱,從而訪問它。
SQL Server Express 位于 Docker Hub 上 Microsoft 維護的映像中。為了通過此映像啟動數據庫容器,我將運行以下代碼:
docker run --detach `--publish 1433:1433 `--env sa_password=MSDNm4g4z!n3 `--env ACCEPT_EULA=Y `--name sql-server `microsoft/mssql-server-windows-express這會在后臺啟動具有拆離標記的容器,并發布端口 1433,以便我可以從外部連接容器中的 SQL 實例(可能在主機上使用 SQL Server Management Studio)。env 選項是鍵值對,Docker 在容器內將其公開為系統環境變量。SQL Server 映像使用這些值來確認許可協議是否已被接受,并為 sa 用戶設置密碼。
Docker 必須先在本地復制映像,然后才能運行容器。分發內容會在 Docker 平臺中生成。因此,如果在運行此命令時沒有本地 SQL Server Express 映像,Docker 將會從 Hub 下載。Docker Hub 上有超過 50 萬個映像,這些映像已被下載超過 90 億次。Docker 始于 Linux,其中大部分映像是 Linux 應用程序,但優質的 Windows 應用程序也越來越多,可供你下載并直接應用到解決方案中。
現在,SQL Server 在 Docker 容器中運行,我的 Web 應用程序在連接字符串中將 sql-server 指定為主機名,以便連接在 Docker 中運行的數據庫。我可以在后臺啟動 WebForms 應用程序,并發布端口 80,讓網站可供訪問:
docker run --detach `--publish 80:80 `sixeyed/msdn-web-app:v1如果外部計算機在端口 80 上向我的主機發送請求,Docker 會接收請求,并透明地將請求轉發給容器中運行的 ASP.NET 應用程序。如果我使用的是主機,則需要運行“docker inspect”獲取容器的 IP 地址,然后轉到容器即可顯示網站(這是一個簡單的產品發布微站)。圖 1展示了在 Docker 中運行的網站的數據捕獲頁面。
圖 1:在 Docker 中運行的網站的注冊頁
運行“docker ps”將列出所有正在運行的容器。一個是數據庫,另一個是 Web 應用程序,但可以相同方式對兩者進行管理:運行“docker top”可以查看在容器中運行的進程;運行“docker logs”可以查看應用程序的日志輸出;運行“docker inspect”可以查看公開的端口以及有關容器的其他許多信息。一致性是 Docker 平臺的主要優勢。可以相同方式打包、分發和管理應用程序,無論其使用什么技術。
拆分整個應用程序的功能
至此,應用程序已在新式平臺上運行,我可以開始讓應用程序本身現代化了。雖然將整個應用程序細分成較小服務的工作量非常浩大,但可以采取更有針對性的方法,將重點放在關鍵功能(如定期變化的功能)上,這樣就可以部署有變化的功能的更新,而無需對整個應用程序執行回歸測試。具有非功能性要求的功能可以受益于另一種設計(即無需對應用程序進行完全的體系結構重建),也是合適之選。
我將從修復性能問題入手。在現有代碼中,應用程序同步連接數據庫來保存用戶數據。這種方法的擴展性不佳。也就是說,如果有許多并發用戶,就會造成 SQL Server 瓶頸。與消息隊列進行異步通信是更具擴展性的設計。對于此功能,我可以將 Web 應用程序中的事件發布到消息隊列,然后將數據暫留代碼移到用于處理此事件消息的新組件中。
此設計確實也具有很好的擴展性。如果出現網站流量高峰,我可以在更多主機上運行更多容器,以處理傳入的請求。在消息處理程序處理事件消息前,它們會一直保留在隊列中。對于沒有特定 SLA 的功能,可以在一個容器中運行一個消息處理程序,并依賴消息隊列的保證,即所有事件最終都會得到處理。對于 SLA 驅動型功能,可以通過運行更多的消息處理程序容器來擴展暫留層。
本文隨附的源代碼包含應用程序版本 1、2 和 3 的文件夾。在版本 2 中,SignUp.aspx 頁面在用戶提交詳細信息表單時發布事件:
var eventMessage = new ProspectSignedUpEvent {Prospect = prospect,SignedUpAt = DateTime.UtcNow }; MessageQueue.Publish(eventMessage);此外,在版本 2 中,有一個共享的消息傳送項目,用于提取消息隊列的詳細信息;還有一個控制臺應用程序,用于偵聽 Web 應用程序發布的事件,并將用戶數據保存到數據庫。控制臺應用程序中的暫留代碼直接取自 Web 應用程序中的版本 1 代碼,所以實現代碼是一樣的,不同之處在于功能設計已經過現代化。
應用程序的新版本是包含許多工作部件的已分發解決方案,如圖 2?所示。
圖 2:經過現代化的應用程序包含許多工作部件
組件之間有依賴項,必須以正確的順序啟動,這樣解決方案才能正常運行。這是安排跨許多容器運行的應用程序的業務流程時面臨的問題之一,而為了解決此問題,Docker 平臺將已分發的應用程序視作“一等公民”。
使用 Docker Compose 安排應用程序的業務流程
Docker Compose 屬于 Docker 平臺,主要處理對象是已分發的應用程序。在簡單文本文件中將應用程序的所有部分定義為各個服務,包括組件之間的依賴項及其需要的所有配置值。下面展示了部分版本 2 Docker Compose 文件,僅包含 Web 應用程序的配置:
product-launch-web:image: sixeyed/msdn-web-app:v2ports:- "80:80"depends_on:- sql-server- message-queuenetworks:- app-net此時,我要指定要對 Web 應用程序使用的映像版本。我發布端口 80,然后顯式聲明 Web 應用程序依賴 SQL Server 和消息隊列容器。Web 容器必須位于同一虛擬 Docker 網絡中,才能訪問這些容器。因此,Docker Compose 文件中的所有容器都會聯接到同一虛擬網絡 app-net 中。
在 Docker Compose 文件中的其他位置,我使用 Docker Hub 上的 Microsoft 映像定義 SQL Server 服務,并使用 NATS 消息傳送系統定義消息隊列服務(這是性能卓越的開放源代碼消息隊列)。NATS 是 Docker Hub 上的官方映像。最終定義的是消息處理程序服務,這是使用簡單的 Dockerfile 打包成 Docker 映像的 .NET 控制臺應用程序。
現在,我可以使用以下 Docker Compose 命令行運行應用程序:
docker-compose up -dDocker Compose 會按正確的順序啟動每個組件的容器,只需一個命令就可以為我提供有效的解決方案。有權訪問 Docker 映像和 Docker Compose 文件的任何人都可以運行應用程序,且行為方式是相同的,無論是在 Windows 10 筆記本電腦上,還是在數據中心或 Azure 中運行的 Windows Server 2016 計算機上。
對于版本 2,我稍微更改了一下應用程序代碼,將功能實現代碼從一個組件移到另一個組件。雖然最終用戶行為方式是相同的,但現在解決方案易于擴展,因為 Web 層與數據層分離,消息隊列負責處理任何流量峰值。新設計也易于擴展,因為我引入了事件驅動型體系結構,從而可以通過聯接現有事件消息來觸發新行為。
添加自助式分析
對于我的示例應用程序,我將再更改一下代碼,以說明使用 Docker 平臺只需極少的工作即可實現很多功能。應用程序當前使用 SQL Server 作為事務數據庫,我將添加第二個數據存儲作為報表數據庫。這樣一來,我可以單獨處理報表和事務問題,并能自由選擇技術堆棧。
在示例代碼的版本 3 中,我添加了新的 .NET 控制臺應用程序,用于偵聽 Web 應用程序發布的同一事件消息。當兩個控制臺應用程序同時運行時,NATS 消息隊列會確保兩個應用程序都能獲得所有事件的副本。新的控制臺應用程序會接收事件,并在 Elasticsearch(可以在 Windows Docker 容器中運行的開放源代碼文檔存儲)中保存用戶數據。此時,Elasticsearch 是理想之選,既因為它具有良好的擴展性,以便我能夠出于冗余考慮跨多個容器對它進行匯集,也因為它提供了十分有用的面向用戶的 Kibana 前端。
由于自版本 2 我沒有對 Web 應用程序或 SQL Server 消息處理程序進行任何更改,所以在 Docker Compose 文件中,我僅為 Elasticsearch 和 Kibana 以及將文檔寫入 Elasticsearch 索引的新消息處理程序添加新服務:
index-prospect-handler:image: sixeyed/msdn-index-handler:v3depends_on:- elasticsearch- message-queuenetworks:- app-netDocker Compose 可以對應用程序進行增量升級,不會替換其定義與 Docker Compose 文件中的服務匹配的正在運行的容器。示例應用程序的版本 3 中新增了服務,但沒有對現有服務進行更改。因此,當我運行 docker-compose up –d 時,Docker 會為 Elasticsearch、Kibana 和索引消息處理程序運行新容器,而其他服務的容器則按原樣運行,這就構成了非常安全的升級進程,無需讓應用程序脫機,即可添加功能。
此應用程序更傾向于約定,而不是配置。因此,依賴項(如 Elasticsearch)的主機名在應用程序中設置為默認名稱,我只需確保容器名稱在 Docker Compose 設置保持一致即可。
新容器啟動后,我可以運行“docker inspect”獲取 Kibana 容器的 IP 地址,然后轉到此地址上的端口 5601。Kibana 有一個非常簡單的接口,我可以在幾分鐘內就生成一個儀表板,用于顯示使用詳細信息登錄的用戶的關鍵指標,如圖 3?所示。
圖 3:Kibana 儀表板
Power User 很快就可以上手使用 Kibana,能夠制作自己的可視化效果和儀表板,而無需涉及 IT 層面。在沒有任何故障時間的情況下,我就向應用程序添加了自助式分析。此功能的核心源于我從 Docker Hub 拉取到解決方案中的企業級開放源代碼軟件。向文檔存儲提供數據的自定義組件是簡單的 .NET 控制臺應用程序,只需約 100 行代碼即可實現。Docker 平臺負責將組件聯接在一起。
在 Azure 上運行經過 Docker 處理的解決方案
Docker 的另一大優勢是可移植性。打包到 Docker 映像中的應用程序的運行方式與在任何主機上的運行方式完全相同。本文最終生成的應用程序使用 Microsoft 擁有的 Windows Server 和 SQL Server 映像、Docker 管理的 NATS 映像和我自己的自定義映像。所有這些映像均在 Docker Hub 上發布,因此任何 Windows 10 或 Windows Server 2016 計算機均可拉取映像,并通過這些映像運行容器。
現在,我的應用程序已可供測試,將其部署到 Aure 上的共享環境十分簡單。我通過結合使用 Windows Server 2016 Datacenter 和“容器”選項,在 Azure 中創建了虛擬機 (VM)。在 VM 映像中,已安裝并配置 Docker,并且已下載 Windows Server Core 和 Nano Server 的基本 Docker 映像。VM 中未包含的一項是 Docker Compose,我已從 GitHub 發布頁進行下載。
我的 Docker Compose 文件中使用的映像均位于 Docker Hub 上的公用存儲庫中。如果是私有軟件堆棧,你可能并不希望所有映像都公開。仍可以使用 Docker Hub,并將這些映像保留在私有存儲庫中,也可以使用托管的注冊表,如 Azure 容器注冊表。在你自己的數據中心內,可以使用本地選項,如 Docker 信任的注冊表。
由于我的所有映像都是公開的,因此我只需將 Docker Compose 文件復制到 Azure VM,然后運行 docker-compose up –d 即可。Docker 會從 Hub 拉取所有映像,并按正確的順序通過這些映像運行容器。每個組件均使用約定來訪問其他組件,這些約定已內置到 Docker Compose 文件中。因此,即使是在全新的環境中,解決方案也仍會按預期啟動和運行。
如果使用的是企業軟件版本,即設置新環境是有風險的緩慢手動進程,便能感受到 Windows Server 2016 和 Docker 平臺帶來的巨大優勢。Docker 解決方案中的關鍵項目(Dockerfile 和 Docker Compose 文件)可直接明確替代手動部署文檔。這兩個關鍵項目倡導的是自動化操作,可方便你在任何一臺計算機上一致地生成、傳送和運行解決方案,整個過程非常簡單。
后續步驟
如果你熱衷于親自嘗試 Docker,最好從 Image2Docker PowerShell 模塊入手;它可以為你生成 Dockerfile,讓你快速開始學習。training.docker.com?上免費提供了一些自主掌控進度的優質課程,這些課程為你預配了環境。然后,若要繼續深入,請查看 GitHub 上的 Docker 實驗室,其中提供了許多 Windows 容器演練。
世界各地都有 Docker 聚會,你可以聆聽從業人員和專家談論 Docker 的方方面面。Docker 盛會 DockerCon 總是座無虛席。今年將于 4 月和 10 月分別在德克薩斯州和哥本哈根市舉行。最后,請關注 Docker Captain(在 Docker 領域等同于 Microsoft MVP)。他們經常在博客和 Twitter 上介紹他們使用 Docker 所實現的炫酷功能,關注他們可以有效把握技術脈搏。
Elton Stoneman?連續七屆榮獲 Microsoft MVP,不僅是 Pluralsight 作者,還是 Docker 的開發大使。自 2000 年起,他就一直在使用 Microsoft 技術構造并交付成功的解決方案,最近的工作涉及 API 和 Azure 大數據項目,以及使用 Docker 的已分發應用程序。
衷心感謝以下技術專家對本文的審閱: Mark Heath
Mark Heath 是專注于 Azure 的 .NET 開發者,不僅是 NAudio 的創建者,還是 Pluralsight 作者。可以在他的博客 (markheath.net) 和 Twitter (@mark_heath) 上關注他。
相關文章:
老司機實戰Windows Server Docker:1 初體驗之各種填坑
老司機實戰Windows Server Docker:2 docker化現有iis應用的正確姿勢
老司機實戰Windows Server Docker:3 單節點Windows Docker服務器簡單運維(上)
.Net大戶的選擇:Windows Container在攜程的應用
Docker4Dev #6 使用 Windows Container 運行.net應用
Docker基礎入門及示例
Linux+Nginx+Asp.net Core部署
老司機實戰Windows Server Docker:4 單節點Windows Docker服務器簡單運維(下)
老司機實戰Windows Server Docker:5 Windows Server Dockerfile葵花寶典
ASP.NET Core 網站在Docker中運行
.NET遇上Docker - 使用Docker Compose組織Ngnix和.NETCore運行
.NET程序在Linux容器中的演變
Windows Server Containers 支持 Windows 開發者使用 Docker
原文地址:https://msdn.microsoft.com/en-us/magazine/mt797650
.NET社區新聞,深度好文,微信中搜索dotNET跨平臺或掃描二維碼關注
總結
以上是生活随笔為你收集整理的使用 Docker 让传统 .NET 应用程序现代化的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ASP.Net防范XSS漏洞攻击的利器H
- 下一篇: .NET的一点历史故事:作者的一些感想