最近新專案使用了.NET 6、Docker、K8S等技術,用.NET 6建立新專案的同時,對.NET 6多了一些認識,也在容器化的過程中遇到很多有趣的坑,特別將操作流程做個紀錄。
環境安裝流程
.NET SDK
至.NET官網下載.NET 6 SDK
Docker Desktop
至Docker官網下載Docker Desktop
環境檢視入門
確認安裝是否成功
開啟cmd 或powershell,輸入下列指令
dotnet --version
docker --version
容器建立流程
流程介紹
要建立一個可連線的.NET 6容器,大致可分為4個重要的步驟(圖1)。
- .NET 6相關流程: 包含建立專案、安裝套件、引用套件,及其他客製化設定等。
- 撰寫Dockerfile
- 製作映像檔
- 建立並啟動容器
.NET 6 相關流程
建立.NET 6新專案
dotnet new webapi -o Testapi
dotnet new sln
dotnet sln add Testapi.csproj
安裝Package
至Nuget官網,查詢可用的Package版本號,並使用dotnet add package下載package,例如
dotnet add package Microsoft.EntityFrameworkCore
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
因.NET會將package放在 C:/User/.nuget/packages 目錄中,如果已經安裝過套件,只需要在.csproj檔案中加入PackageReference即可(圖2)。
或使用下列語法進行套件版本引用設定
dotnet add TestAPI.csproj package Microsoft.EntityFrameworkCore -v 7.0.5
註冊服務並客製化相關的設定
設定連線字串
builder.Services.AddDbContext<CommonDataContext>(options =>
{
options.UseSqlServer(builder.Configuration.GetConnectionString("YourConnectionString"));
});
註冊Swagger 並客製化Header
builder.Services.AddSwaggerGen(c =>
{
c.OperationFilter<SwaggerHeaderFilter>();
});
實作IOperationFilter
public class SwaggerHeaderFilter : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
if (operation.Parameters == null)
{
operation.Parameters = new List<OpenApiParameter>();
}
IDictionary<string, OpenApiExample> langs = new Dictionary<string, OpenApiExample>();
langs.Add("繁中", new OpenApiExample() { Value = new OpenApiString("zh-TW") });
langs.Add("簡中", new OpenApiExample() { Value = new OpenApiString("zh-CN") });
langs.Add("英文", new OpenApiExample() { Value = new OpenApiString("en-US") });
operation.Parameters.Add(
new OpenApiParameter
{
Name = "Accept-Language",
In = ParameterLocation.Header,
Required = true,
Schema = new OpenApiSchema { Type = "string" },
Examples = langs
}
)
}
}
註冊NLog
builder.Logging.ClearProviders();
builder.Host.UseNLog();
用.NET 6 建立專案時的差異及釐清的概念(
官網列出的差異: https://learn.microsoft.com/zh-tw/aspnet/core/migration/50-to-60-samples?view=aspnetcore-7.0
- 捨棄StartUp.cs:
.NET 6僅有Program.cs
- 新增GlobalUsing的功能:
開啟.csproj可看到ImpliciutUsings初始設定為enable,會導入微軟預設的GlobalUsings.cs,並在檔案內。若要自訂義GlobalUsing,則自行建立全新的GlobalUsings.cs檔案,並使用global using設定要全域使用的命名空間。流程可參考。
- 對Null的設定改變:
.csproj可設定Null的特性,例如<Nullable>disable</Nullable />,EntityFramework Context物件,可null時也要加上?,例如 public string? Request {get; set;}
- dotnet publish -c release
若/bin/Release/net6.0/publish已存在,則大小寫沿用已有路徑大小寫,但第一次建立時,
dotnet publish -c release
dotnet publish -c Release
建立的/Release/資料夾大小寫會不同,撰寫Dockerfile 時,會出現複製路徑找不到的坑。
- .指定SDK進行build
若電腦中安裝多個SDK版本,預設會使用最新版的SDK建置,可查詢電腦中的SDK版本,並在專案中建立global.json,指定build要使用的SDK版本。
dotnet --list-sdks
{ "sdk": {
"version": "5.0.001"
}
}
撰寫Dockerfile
理解Dockerfile內容
FROM: 使用的基礎映像,如mcr.microsoft.com/dotnet/sdk:6.0
WORKDIR: 設定容器內的工作目錄
COPY: 複製檔案,可從本機複製至容器內,也可在容器中互相複製
RUN: 執行可用指令,如RUN dotnet build
ENTRYPOINT與CMD: 執行可帶參數的CMD指令,參考
# 建置編譯階段 Image, from dotnet/sdk, 並指定工作目錄為 /source
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
WORKDIR /source
# COPY 本機的所有檔案到 build image source 目錄下, 並執行套件還原指令, 此步驟需連網
COPY . .
RUN dotnet restore
# 執行 dotnet build 指令
RUN dotnet build
# 執行 dotnet 發佈指令, 並指定為 Release 版本
RUN dotnet publish -c release --no-restore
# 建置執行階段 Image, from dotnet/aspnet image, 並指定工作目錄為 /app
FROM mcr.microsoft.com/dotnet/aspnet:6.0
ENV TZ="Asia/Taipei"
WORKDIR /app
# COPY 編譯階段已產生的發佈檔至 /app 下
COPY --from=build /source/bin/release/net6.0/publish .
# 安裝 ping 及 telnet 指令, 方便有問題時在 Container 內下指令排除
RUN apt-get update
RUN apt-get install -y iputils-ping
RUN apt-get install -y telnet
ENTRYPOINT ["dotnet", "TestAPI.dll"]
Listen on Port被改變的坑
原Dockerfile範例的順序邏輯,是將Source Code複製進Container,以一個基礎映像檔進行dotnet build及dotnet publish,最後將publish產出的dll檔複製到另一個基礎映像檔,此時監聽的port是80(圖3)。
因某些因素,阿猩修改了Dockerfile的順序邏輯,阿猩在自己的本地端進行dotnet publish,直接將dll檔複製到容器內。但發現監聽的port居然變成5000。原因是,若無launchSetting.json或其他的設定時,.NET預設port為5000,Port的設定及優先順序可參考此篇
後來阿猩發現Dockerfile可直接以ENV進行環境設定,例如
ENV TZ="Asia/Taipei"
阿猩成功以ENV修改容器監聽的Port
ENV ASPNETCORE_URLS=http://+:80 \
DOTNET_RUNNING_IN_CONTAINER=true
Swagger無法顯示的問題
原Program中的Swagger Middleware會判斷當前的環境是否為Development(圖4)。
在launchSetting.json中可設定Development,例如使用偵錯模式、IIS啟動時,預設環境為Developmenet(圖5)。
因此在偵錯模式時,輸入localhost:7129/Swagger/index.html,可以看到Swagger UI的,但以容器啟用會看不到。
除了設定launchSetting.json之外,也可以直接在Dockerfile中加入
ENV ASPNETCORE_ENVIRONMENT=Development
製作映像檔
建立Image
開啟cmd或powershell,至dockerfile所在路徑,執行
docker build -t tag名稱或映像名稱 .
建立並啟動容器
啟用Container
開啟cmd或powershell,執行
docker run --name 容器名稱 -p 8080:80 映像檔名稱
參考資料
- https://dotnet.microsoft.com/en-us/download/dotnet/6.0
- https://docs.docker.com/engine/reference/builder/
- https://www.nuget.org/3
- https://www.huanlintalk.com/2022/02/csharp10-global-using.html
- https://myapollo.com.tw/blog/docker-cmd-vs-entrypoint/
- https://blog.yowko.com/aspdotnet-core-urls-setting-sequence/