ASP.NET Core 專案容器化實踐

本篇整理 ASP.NET Core 專案在容器化時可能碰到的問題和相關解法。

ASP.NET Core 設定

原本在 ASP.NET Core MVC 中,設定檔多從 appsettings.json 中讀取,而 Docker 的 best practice 是使用環境變數來靈活調整設定,好像衝突了?

微軟官方的這篇文章中可以找到使用環境變數取代 appsettings.json 的作法。

1
2
3
var builder = WebApplication.CreateBuilder(args);
# 從環境變數中讀取設定
builder.Configuration.AddEnvironmentVariables();

環境變數的優先度很高,只要有重複的設定都會被環境變數蓋掉

假設原本的 appsettings.json 長下面這樣

1
2
3
4
5
6
7
8
9
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}

轉換成環境變數的寫法可以這樣寫(如果你是寫 env file 給 Docker 吃的話記得不要加雙引號,它會把雙引號也吃進去)

1
2
3
Logging__LogLevel__Default=Information
Logging__LogLevel__Microsoft.AspNetCore=Warning
AllowedHosts=*

可以注意到平常 json 存取會用到的 . 可以換成 __ (兩個底線)。官方文件中也有可以讓環境變數加 prefix 的作法,如果環境變數名稱衝突的話可以試試看。

另外有個魔法環境變數 ASPNETCORE_ENVIRONMENT,它是 ASP.NET Core 用來確定當前運作環境用的,改成 Development 就會變成開發模式,如果為空或是 Production 就會是正式模式。

那 Database 連接字串這類要怎麼解決呢? 這裡有詳細的介紹。

常用的幾個

  • MySQL
    • MYSQLCONNSTR_{KEY}
  • MS SQL Server
    • SQLCONNSTR_{KEY}

其實用 ConnectionStrings__{你設的 Key} 實測後也可以

Dockerfile 撰寫

原則上可以照著下面的範本寫,這個範本把 image 拆成兩個階段來減少最終 image 大小,如果還有需求可以自己改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# https://hub.docker.com/_/microsoft-dotnet
# 這個 image 比較肥,有把 sdk 放進來
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
WORKDIR /source

# copy csproj and restore as distinct layers
# sln (解決方案檔)很重要,一定要傳進去
COPY *.sln .

# 這邊是在複製你的 csproj (專案設定檔) 到對應的資料夾
# 這樣就不用把整個解決方案都傳進來,只要傳你會用到的就好
COPY ExampleProject/*.csproj ./ExampleProject/

# 把 nuget 的套件抓回來
# 注意到這裡指定了兩個套件源,因為如果自訂套件源後沒把預設的補回去,它就會把預設的蓋掉
RUN dotnet restore -s https://api.nuget.org/v3/index.json -s https://custom.nuget.source/ ./ExampleProject/ExampleProject.csproj

# copy everything else and build app
# 這階段會把全部檔案都傳進去,如果有不想傳進去的檔案可以手動寫或用 .dockerignore 忽略掉
COPY ExampleProject/ ./ExampleProject/
WORKDIR /source/ExampleProject

# 等同發布
RUN dotnet publish -c release -o /app --no-restore

# final stage/image
# 這個 image 沒有 sdk,所以只能執行不能編譯
FROM mcr.microsoft.com/dotnet/aspnet:6.0
WORKDIR /app

# 把上個階段發布的程式全部傳進來
COPY --from=build /app ./
EXPOSE 80

# 執行
ENTRYPOINT ["dotnet", "ExampleProject.dll"]

寫完要可以放在專案的資料夾或解決方案的資料夾,之後要 build 的時候指令如下:

1
2
3
4
# 你放在解決方案的資料夾內
docker build -f ./Dockerfile -t <tag name>
# 你放在專案的資料夾內
docker build -f ./Dockerfile -t <tag name> ..

注意相對路徑問題

.dockerignore 設定

在 build context 的資料夾(應該會是你放 sln 的那個資料夾)裡面放 .dockerignore,內容跟下面範本一樣應該沒問題

1
2
3
4
# discard IDE compiled files
**/bin/
**/obj/
*.env

因為 Visual Studio 編譯會產生上面那兩個放 binary 的資料夾,不 ignore 掉的話會讓 build context 變得很大,導致 build 時間很久。

在執行時傳遞環境變數

容器的環境變數可以在執行前指定

1
2
3
4
# 很少的話用這個即可
docker run -e AllowedHosts=* <image>
# 你有個 env file 可以讀取可以用這個
docker run --env-file .env <image>