让我的 .NET Core 博客系统支持 Docker
點(diǎn)擊上方藍(lán)字關(guān)注“汪宇杰博客”
導(dǎo)語(yǔ)
我的博客(https://edi.wang)所使用的博客系統(tǒng) Moonglade 開(kāi)源已經(jīng)一年多了。目前已有至少4位社區(qū)朋友使用此系統(tǒng)在 Azure、阿里云上部署了自己的博客??上чL(zhǎng)久以來(lái)該系統(tǒng)一直缺乏 Docker 支持,而 .NET Core 必須結(jié)合 Docker 才是當(dāng)今世界的政治正確。我作為一名20年的老軟粉,雖然嘴上說(shuō)著很不情愿用 Linux、Docker這種非微軟的東西,但也只能假裝抱著批判的態(tài)度,向 Linux 和 Docker 伸出了魔爪,讓我的博客系統(tǒng)能夠容器化運(yùn)行。
Docker 環(huán)境安裝
我作為一個(gè)20多年的老軟粉,怎么可以在自己純潔的 Windows 電腦上裝 Docker 呢?裝完以后:Docker 真香。
為了最大限度的避免 Windows 被污染(盡管它已經(jīng)是咖喱拌飯了),我的 Docker 編譯和發(fā)布環(huán)境都配置在云端,采用 Azure DevOps + Docker Hub + Azure App Service Linux Plan 的方式去編譯運(yùn)行。
Dockerfile
Visual Studio 可以直接右鍵一個(gè) ASP.NET Core 項(xiàng)目添加 Docker 支持,這種方式可以讓你很方便的在本地調(diào)試 Docker 中的 ASP.NET Core 程序。VS除了向工程目錄添加一個(gè) Dockerfile 以外,還會(huì)修改你的 csproj 工程文件,好讓工具鏈整合你的容器。而其實(shí)對(duì)于單純編譯和運(yùn)行 ASP.NET Core 網(wǎng)站而言,單獨(dú)一個(gè) Dockerfile 就夠了,Docker 會(huì)根據(jù)這個(gè) Dockerfile 編譯出應(yīng)用的容器鏡像。
最初我博客的 Dockerfile 內(nèi)容如下:
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 其余的工程文件,篇幅關(guān)系省略
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 自動(dòng)生成的沒(méi)太大區(qū)別。其中,我指定編譯版本號(hào)參數(shù)為?-p:Version=10.2.0-docker,以便于直接從博客網(wǎng)站的界面分辨部署類(lèi)型是 Docker 還是傳統(tǒng)的 Code。
YAML
在 Azure DevOps 上,我使用 YAML 方式編譯和部署我的博客項(xiàng)目,其中 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,因此這個(gè) Docker 鏡像編譯出來(lái)為 Linux/x64 架構(gòu)。如果你需要其他架構(gòu),可以自行添加其他類(lèi)型的VM鏡像。
ediwang_dockerhub 是預(yù)先在 Azure DevOps 授權(quán)配置好的針對(duì) Docker Hub 的連接名稱(chēng)。編譯完成后,Azure DevOps 會(huì)使用其中的授權(quán)向 Docker Hub 發(fā)布鏡像。
福報(bào)#1:路徑問(wèn)題
當(dāng)我興高采烈的測(cè)試我的 Docker 容器時(shí),我驚喜的發(fā)現(xiàn),博客的博主頭像、RSS訂閱、OPML等全部都404了。根據(jù)之前我修過(guò)的Linux福報(bào),我立即明白這是路徑寫(xiě)法的問(wèn)題。
在 Windows 系統(tǒng)中,表示一個(gè)文件或文件夾的路徑通常用反斜杠分割目錄,如:
C:\Fubao\996.icu
而 Linux 系統(tǒng)中,路徑得用斜杠來(lái)分割目錄,如:
/use/dotnet/work/955
像我這樣的老牌軟狗,很容易按照習(xí)慣把代碼寫(xiě)成 Windows 的形式,畢竟微軟曾經(jīng)說(shuō)好的 Linux 是毒瘤, .NET 只能在 Windows 上跑:
var fallbackImageFile = $@"{AppDomain.CurrentDomain.GetData(Constants.AppBaseDirectory)}\wwwroot\images\default-avatar.png";
其實(shí) .NET 自古以來(lái)都有個(gè)API:Path.Combine(),用來(lái)拼路徑,它在 .NET Core 里遇到 Linux 環(huán)境可以正確使用斜杠,于是軟狗以為這樣寫(xiě)就沒(méi)事了:
var cssPath = Path.Combine(webRootPath, "css", "theme", currentTheme);
大部分情況確實(shí)是好的,然而我們來(lái)看個(gè)會(huì)爆的例子:
var p1 = "/dotnet";
var p2 = "/fubao/996.icu";
Path.Combine(p1, p2);
猜猜結(jié)果變成什么?
/dotnet 被丟掉了,只能996,進(jìn)ICU。
好在微軟為了不讓我們進(jìn)ICU,在.NET Standard 2.1里引入了 Path.Join() 方法,可以輸出我們想要的結(jié)果:
因此,我把博客代碼里用到路徑的地方全部都用 Path.Join() 改了一遍,終于恢復(fù)了博主頭像、RSS等資源的正常訪問(wèn)。
Path.Join() 參考文檔:https://docs.microsoft.com/en-us/dotnet/api/system.io.path.join?view=netcore-3.1
福報(bào)#2:libgdiplus
博客程序運(yùn)行期間,還報(bào)了另一個(gè)錯(cuò),日志如下:
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 系統(tǒng)上裝一個(gè)叫做 libgdiplus 的庫(kù)??墒?Azure App Service 的 Linux 容器主機(jī)對(duì)用戶來(lái)說(shuō)無(wú)法直接操作,不可能 SSH 進(jìn)去給它裝個(gè)庫(kù),怎么辦呢?
Bing 了一番之后發(fā)現(xiàn),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
...
配置默認(rèn)值
使用 Docker 容器部署應(yīng)用的體驗(yàn)我希望是一鍵部署以后啥都不用干,直接能跑。而以前版本的博客系統(tǒng),必須要求用戶先手工配置一堆環(huán)境變量或是配置文件才能跑,非常996。
這個(gè)問(wèn)題非常好辦,只要在 appsettings.json 中留配置的默認(rèn)值,保證程序能先跑起來(lái)即可。至于自定義的配置,可以讓用戶通過(guò)環(huán)境變量傳給 Docker 容器。即保證了一鍵部署的方便性,又保留了自定義配置的靈活性。
小結(jié)
讓 .NET Core 程序支持 Docker 并不麻煩。麻煩的是老一代 .NET 程序員會(huì)被根深蒂固的 Windows 設(shè)計(jì)所牽絆。在新的時(shí)代,我們必須學(xué)習(xí)新的實(shí)踐,不要想著吃老本。.NET Framework 已經(jīng)日薄西山,及時(shí)刪庫(kù)跑路,上 .NET Core + Docker 的船,才能保證在新的時(shí)代還能繼續(xù)用 C# 釋放生產(chǎn)力!我的 Docker 之旅剛剛起步,肯定還有很多我沒(méi)遇到過(guò)的情況。歡迎讀者在留言中補(bǔ)充和建議!
總結(jié)
以上是生活随笔為你收集整理的让我的 .NET Core 博客系统支持 Docker的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 中国速度之二神山建设(2):完善的项目计
- 下一篇: .NET Core下的开源分布式任务调度