让我的 .NET Core 博客系统支持 Docker
點擊上方藍字關注“汪宇杰博客”
導語
我的博客(https://edi.wang)所使用的博客系統 Moonglade 開源已經一年多了。目前已有至少4位社區朋友使用此系統在 Azure、阿里云上部署了自己的博客??上чL久以來該系統一直缺乏 Docker 支持,而 .NET Core 必須結合 Docker 才是當今世界的政治正確。我作為一名20年的老軟粉,雖然嘴上說著很不情愿用 Linux、Docker這種非微軟的東西,但也只能假裝抱著批判的態度,向 Linux 和 Docker 伸出了魔爪,讓我的博客系統能夠容器化運行。
Docker 環境安裝
我作為一個20多年的老軟粉,怎么可以在自己純潔的 Windows 電腦上裝 Docker 呢?裝完以后:Docker 真香。
為了最大限度的避免 Windows 被污染(盡管它已經是咖喱拌飯了),我的 Docker 編譯和發布環境都配置在云端,采用 Azure DevOps + Docker Hub + Azure App Service Linux Plan 的方式去編譯運行。
Dockerfile
Visual Studio 可以直接右鍵一個 ASP.NET Core 項目添加 Docker 支持,這種方式可以讓你很方便的在本地調試 Docker 中的 ASP.NET Core 程序。VS除了向工程目錄添加一個 Dockerfile 以外,還會修改你的 csproj 工程文件,好讓工具鏈整合你的容器。而其實對于單純編譯和運行 ASP.NET Core 網站而言,單獨一個 Dockerfile 就夠了,Docker 會根據這個 Dockerfile 編譯出應用的容器鏡像。
最初我博客的 Dockerfile 內容如下:
FROM?mcr.microsoft.com/dotnet/core/aspnet:3.1-buster-slim?AS?base
WORKDIR?/app
EXPOSE?80
EXPOSE?443
FROM?mcr.microsoft.com/dotnet/core/sdk:3.1-buster?AS?build
WORKDIR?/src
COPY?["Moonglade.Web/Moonglade.Web.csproj",?"Moonglade.Web/"]
# COPY 其余的工程文件,篇幅關系省略
RUN?dotnet?restore?"Moonglade.Web/Moonglade.Web.csproj"
COPY?.?.
WORKDIR?"/src/Moonglade.Web"
RUN?dotnet?build?"Moonglade.Web.csproj"?-p:Version=10.2.0-docker?-c?Release?-o?/app/build
FROM?build?AS?publish
RUN?dotnet?publish?"Moonglade.Web.csproj"?-p:Version=10.2.0-docker?-c?Release?-o?/app/publish
FROM?base?AS?final
WORKDIR?/app
COPY?--from=publish?/app/publish?.
ENTRYPOINT?["dotnet",?"Moonglade.Web.dll"]
這份 Dockerfile 和 Visual Studio 自動生成的沒太大區別。其中,我指定編譯版本號參數為?-p:Version=10.2.0-docker,以便于直接從博客網站的界面分辨部署類型是 Docker 還是傳統的 Code。
YAML
在 Azure DevOps 上,我使用 YAML 方式編譯和部署我的博客項目,其中 Docker 的編譯步驟定義如下:
-?job:?Docker
??pool:
????vmImage:?'ubuntu-latest'
??steps:
??-?task:?Docker@2
????displayName:?'Build?and?Push'
????inputs:
??????containerRegistry:?'ediwang_dockerhub'
??????repository:?ediwang/moonglade
??????tags:?latest
由于VM鏡像選的是 Ubuntu,因此這個 Docker 鏡像編譯出來為 Linux/x64 架構。如果你需要其他架構,可以自行添加其他類型的VM鏡像。
ediwang_dockerhub 是預先在 Azure DevOps 授權配置好的針對 Docker Hub 的連接名稱。編譯完成后,Azure DevOps 會使用其中的授權向 Docker Hub 發布鏡像。
福報#1:路徑問題
當我興高采烈的測試我的 Docker 容器時,我驚喜的發現,博客的博主頭像、RSS訂閱、OPML等全部都404了。根據之前我修過的Linux福報,我立即明白這是路徑寫法的問題。
在 Windows 系統中,表示一個文件或文件夾的路徑通常用反斜杠分割目錄,如:
C:\Fubao\996.icu
而 Linux 系統中,路徑得用斜杠來分割目錄,如:
/use/dotnet/work/955
像我這樣的老牌軟狗,很容易按照習慣把代碼寫成 Windows 的形式,畢竟微軟曾經說好的 Linux 是毒瘤, .NET 只能在 Windows 上跑:
var fallbackImageFile = $@"{AppDomain.CurrentDomain.GetData(Constants.AppBaseDirectory)}\wwwroot\images\default-avatar.png";
其實 .NET 自古以來都有個API:Path.Combine(),用來拼路徑,它在 .NET Core 里遇到 Linux 環境可以正確使用斜杠,于是軟狗以為這樣寫就沒事了:
var cssPath = Path.Combine(webRootPath, "css", "theme", currentTheme);
大部分情況確實是好的,然而我們來看個會爆的例子:
var p1 = "/dotnet";
var p2 = "/fubao/996.icu";
Path.Combine(p1, p2);
猜猜結果變成什么?
/dotnet 被丟掉了,只能996,進ICU。
好在微軟為了不讓我們進ICU,在.NET Standard 2.1里引入了 Path.Join() 方法,可以輸出我們想要的結果:
因此,我把博客代碼里用到路徑的地方全部都用 Path.Join() 改了一遍,終于恢復了博主頭像、RSS等資源的正常訪問。
Path.Join() 參考文檔:https://docs.microsoft.com/en-us/dotnet/api/system.io.path.join?view=netcore-3.1
福報#2:libgdiplus
博客程序運行期間,還報了另一個錯,日志如下:
2020-03-31T12:02:53.405115468Z System.TypeInitializationException: The type initializer for 'Gdip' threw an exception.
2020-03-31T12:02:53.405359877Z ?---> System.DllNotFoundException: Unable to load shared library 'libgdiplus' or one of its dependencies. In order to help diagnose loading problems, consider setting the LD_DEBUG environment variable: liblibgdiplus: cannot open shared object file: No such file or directory
2020-03-31T12:02:53.405375177Z ? ?at System.Drawing.SafeNativeMethods.Gdip.GdiplusStartup(IntPtr& token, StartupInput& input, StartupOutput& output)
2020-03-31T12:02:53.405390778Z ? ?at System.Drawing.SafeNativeMethods.Gdip..cctor()
2020-03-31T12:02:53.405397878Z ? ?--- End of inner exception stack trace ---
這是由于博客代碼用到了一些 .NET Core 的繪圖 API,而這些 API 的底層需要 Linux 系統上裝一個叫做 libgdiplus 的庫。可是 Azure App Service 的 Linux 容器主機對用戶來說無法直接操作,不可能 SSH 進去給它裝個庫,怎么辦呢?
Bing 了一番之后發現,Dockerfile 里面居然可以直接定義 Linux 安裝包的命令,把依賴性搞定。直接加入一條RUN命令的步驟即可:
FROM?mcr.microsoft.com/dotnet/core/aspnet:3.1-buster-slim?AS?base
RUN?apt-get?update?&&?apt-get?install?-y?libgdiplus
WORKDIR?/app
EXPOSE?80
EXPOSE?443
...
配置默認值
使用 Docker 容器部署應用的體驗我希望是一鍵部署以后啥都不用干,直接能跑。而以前版本的博客系統,必須要求用戶先手工配置一堆環境變量或是配置文件才能跑,非常996。
這個問題非常好辦,只要在 appsettings.json 中留配置的默認值,保證程序能先跑起來即可。至于自定義的配置,可以讓用戶通過環境變量傳給 Docker 容器。即保證了一鍵部署的方便性,又保留了自定義配置的靈活性。
小結
讓 .NET Core 程序支持 Docker 并不麻煩。麻煩的是老一代 .NET 程序員會被根深蒂固的 Windows 設計所牽絆。在新的時代,我們必須學習新的實踐,不要想著吃老本。.NET Framework 已經日薄西山,及時刪庫跑路,上 .NET Core + Docker 的船,才能保證在新的時代還能繼續用 C# 釋放生產力!我的 Docker 之旅剛剛起步,肯定還有很多我沒遇到過的情況。歡迎讀者在留言中補充和建議!
總結
以上是生活随笔為你收集整理的让我的 .NET Core 博客系统支持 Docker的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 中国速度之二神山建设(2):完善的项目计
- 下一篇: [ASP.NET Core MVC] 如