---
title: 在 .NET 8 中實現容器化:Docker 應用與經驗分享
tags: [.Net 8,Container,blog]
---
{%hackmd @siugar/blog-article-base-layout %}
:arrow_left: [回到部落格首頁](/@siugar/blog-tw) :arrow_right: <a target="_blank" href="/@siugar/DotNetContainer">本文單篇連結</a>
# 在 .NET 8 中實現容器化:Docker 應用與調試心得
[toc]
## 前言
我之前參與過的一個全新專案,需要運行在google的cloud run,這是我第一個容器化的案子,內部開發服務器以及本機則使用docker desktop來測試與運行。藉此分享一下,如何從無到有建立一個容器化的網站以及經驗分享。
## 本文
### 1. 怎麼讓專案支援container
其實方法很簡單,目前我是安裝Visual studio 2022 最新版本,這邊介紹一下如何建立一個支援Container的.Net 8專案
#### 1.1 專案建立時則立刻選擇專案要支援Container
建立專案時,只要如下圖在`Enable container support` 處勾選即可

#### 1.2 專案已經建立好了,但想事後支援Container
- 1.2.1 對專案點選右鍵開啟選單->[Add]->[選取Docker Support]
<div style="display: flex; justify-content: center; gap: 10px;">
<img src="https://hackmd.io/_uploads/Hk_51jzoyx.png" width="50%">
<span style="display: flex; align-items: center; font-size: 32px;">➡️</span>
<img src="https://hackmd.io/_uploads/rkpqgoMjyl.png" width="55%">
</div>
<br/>
- 1.2.2 完成之後,系統就會自動安裝好IDE整合除錯需要的Package Microsoft.VisualStudio.Azure.Containers.Tools.Targets,並且產生一個預設版本的Dockerfile

### 2. 在IDE運行Container站台與中斷debug
基本上前一個步驟完成後,這邊和一般其他專案的debug是一樣的
點選IDE上方的Container(Dockerfile)啟動站台,畫面如下,

可以中斷在自己加的中斷點進行除錯,下方還會顯示當下運行container的一些資訊包含Logs

### 3. 支援一次開啟多個container站台
要可以一次開啟多個container同時進行debug,需要搭配docker compose專案,透過下面的方法可以建立,並且編寫docker compose來描述多個站台的設定,並且同時開啟。
#### 3.1 首次建立 Docker Compose專案的方法
對專案點選右鍵開啟選單->[Add]->[Container Orchestrator Support]

#### 3.2 初次建立後的畫面
初次建立後的畫面,可以看到docker-compose.yml與launchSettings.json,需要透過這兩個搭配編寫出想要一次性開啟多個站台

#### 3.3 編寫docker-compose.yml與launchSettings.json設定想要一次性開啟哪些站台
- 3.3.1 docker-compose.yml
用來描述每個站台或服務的內容,包含對應的Dockerfile,port,environment, network與volume設定等等..。
```yaml
services:
ehs.etipetslivestream.cms:
image: ${DOCKER_REGISTRY-}ehsetipetslivestreamcms
build:
context: .
dockerfile: Ehs.EtipetsLiveStream.Cms/Dockerfile
ports:
- "50201:8080"
environment:
- ASPNETCORE_ENVIRONMENT=Development
- ASPNETCORE_URLS=http://+:8080
networks:
- mybridge
volumes:
- ${APPDATA}/Microsoft/UserSecrets:/home/app/.microsoft/usersecrets:ro
- ${APPDATA}/ASP.NET/Https:/home/app/.aspnet/https:ro
#- D:\EtPet_Image_To_Nas:/app/share
privileged: true
ehs.etipetslivestream.b2c1:
container_name: ehs_etipetslivestream_b2c1
image: ${DOCKER_REGISTRY-}ehsetipetslivestreamb2c
build:
context: .
dockerfile: Ehs.EtipetsLiveStream.B2C/Dockerfile
#target: development
ports:
- "50101:8080"
environment:
- ASPNETCORE_ENVIRONMENT=Development
- ASPNETCORE_URLS=http://+:8080
- FORCE_USE_LOCAL_REDIRECT=true
networks:
- mybridge
ehs.etipetslivestream.b2c2:
container_name: ehs_etipetslivestream_b2c2
image: ${DOCKER_REGISTRY-}ehsetipetslivestreamb2c
build:
context: .
dockerfile: Ehs.EtipetsLiveStream.B2C/Dockerfile
ports:
- "50102:8080"
environment:
- ASPNETCORE_ENVIRONMENT=Development
- ASPNETCORE_URLS=http://+:8080
- FORCE_USE_LOCAL_REDIRECT=true
networks:
- mybridge
networks:
mybridge:
external: true
```
- 3.3.2 laucnSettings.json
描述IDE上方的啟動選單,用來控制不同的設定對應開啟哪幾個站台(或服務)
```
{
"profiles": {
"Docker Compose(Cms)": {
"commandName": "DockerCompose",
"commandVersion": "1.0",
"composeLaunchAction": "LaunchBrowser",
"composeLaunchServiceName": "cms",
"composeLaunchUrl": "{Scheme}://localhost:{ServicePort}",
"serviceActions": {
"cms": "StartDebugging",
"b2c1": "DoNotStart",
"b2c2": "DoNotStart"
}
},
"Docker Compose(B2C)": {
"commandName": "DockerCompose",
"commandVersion": "1.0",
"composeLaunchAction": "LaunchBrowser",
"composeLaunchServiceName": "b2c1",
"composeLaunchUrl": "{Scheme}://localhost:{ServicePort}",
"serviceActions": {
"b2c1": "StartDebugging",
"b2c2": "DoNotStart",
"cms": "DoNotStart"
}
},
"Docker Compose(B2CX2)": {
"commandName": "DockerCompose",
"commandVersion": "1.0",
"composeLaunchAction": "LaunchBrowser",
"composeLaunchServiceName": "b2c1",
"composeLaunchUrl": "{Scheme}://localhost:{ServicePort}",
"serviceActions": {
"b2c1": "StartDebugging",
"b2c2": "StartDebugging",
"cms": "DoNotStart"
}
},
"Docker Compose(B2CX2+Cms)": {
"commandName": "DockerCompose",
"commandVersion": "1.0",
"composeLaunchAction": "LaunchBrowser",
"composeLaunchServiceName": "b2c1",
"composeLaunchUrl": "{Scheme}://localhost:{ServicePort}",
"serviceActions": {
"b2c1": "StartDebugging",
"b2c2": "StartDebugging",
"cms": "StartDebugging"
}
}
}
}
```
### 4. Dockerfile的重點介紹
#### 4.1 將一些開發需要的套件安裝進入
```dockerfile
RUN apt-get update && \
apt-get install -y curl && \
apt-get install -y iputils-ping && \
apt-get install -y telnet && \
apt-get install -y iproute2 && \
apt-get install -y cifs-utils && \
apt-get install -y sudo
```
#### 4.2 運用分層的方式來控制不同的需求
以下面範例來說,每個紅色的一行表示不同的一層,`FROM base AS development`表示宣告接下來的描述為`development`這一層的內容,並且上一層銜接自`base`
<pre>
<span style="color: red;">FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base</span>
中間截略
####################################################################
<span style="color: red;">FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS production_base</span>
中間截略
####################################################################
<span style="color: red;">FROM production_base AS production</span>
WORKDIR /app
COPY --from=publish /app/publish .
ENV ASPNETCORE_ENVIRONMENT=Production
ENTRYPOINT ["dotnet", "your-project.dll"]
####################################################################
<span style="color: red;">FROM base AS development</span>
WORKDIR /app
COPY --from=publish /app/publish .
ENV ASPNETCORE_ENVIRONMENT=Development
RUN echo "From development"
ENTRYPOINT ["dotnet", "your-project.dll"]
</pre>
附上完整的Dockerfile
```dockerfile
# See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging.
# This stage is used when running from VS in fast mode (Default for Debug configuration)
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
USER app
WORKDIR /app
USER root
# 設置 DEBIAN_FRONTEND 為 noninteractive
ENV DEBIAN_FRONTEND=noninteractive
# 更新包列表並安裝 curl, ping 工具, 網芳套件,sudo
RUN apt-get update && \
apt-get install -y curl && \
apt-get install -y iputils-ping && \
apt-get install -y telnet && \
apt-get install -y iproute2 && \
apt-get install -y cifs-utils && \
apt-get install -y sudo
RUN echo "app ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers
USER app
# 建立 app/share 目錄
RUN mkdir -p /app/share
ENV TZ=Asia/Taipei
###########################################
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS production_base
USER root
RUN apt-get update && apt-get install -y --no-install-recommends \
libicu-dev \
&& rm -rf /var/lib/apt/lists/*
ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=false
USER app
WORKDIR /app
ENV TZ=Asia/Taipei
EXPOSE 8080
EXPOSE 8081
###########################################
# This stage is used to build the service project
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
ARG BUILD_CONFIGURATION=Release
WORKDIR /src
COPY ["your-project-folder/your-project.csproj", "your-project-folder/"]
RUN dotnet restore "./your-project-folder/your-project.csproj"
COPY . .
WORKDIR "/src/your-project.Cms"
RUN dotnet build "./your-project.csproj" -c $BUILD_CONFIGURATION -o /app/build
###########################################
# This stage is used to publish the service project to be copied to the final stage
FROM build AS publish
ARG BUILD_CONFIGURATION=Release
RUN dotnet publish "./your-project.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
###########################################
# This stage is used in production or when running from VS in regular mode (Default when not using the Debug configuration)
FROM production_base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENV ASPNETCORE_ENVIRONMENT=Production
ENTRYPOINT ["dotnet", "your-project.dll"]
###########################################
FROM production_base AS production
WORKDIR /app
COPY --from=publish /app/publish .
ENV ASPNETCORE_ENVIRONMENT=Production
ENTRYPOINT ["dotnet", "your-project.dll"]
###########################################
FROM base AS development
WORKDIR /app
COPY --from=publish /app/publish .
ENV ASPNETCORE_ENVIRONMENT=Development
RUN echo "From development"
ENTRYPOINT ["dotnet", "your-project.dll"]
```
### 5. image建立與遠端更新
#### 5.1 透過batch呼叫建立image
這邊會常用到`--target`,透過這個可以指定Dockerfile內的不同設定,來搭配專案不同的環境設定
```
docker build --target development -t your.project.image.output.name:development -f .\your-project-path\Dockerfile .
```
#### 5.2 將image推到遠端
- 5.2.1 推到遠端的docker desktop
```
docker tag your.project.image.output.name:development your-remote-ip:5000/your.project.image.output.name:development
docker push your-remote-ip:5000/your.project.image.output.name:development
```
- 5.2.2 推到遠端的cloud run
```
set PROJECT_NAME=your.project.name
gcloud run deploy your-service-name --image asia-east1-docker.pkg.dev/your-gcp-project/mc/%PROJECT_NAME%:latest --platform managed --region asia-east1 --vpc-connector connector --allow-unauthenticated
```
## 結論
1. Dockerfile的分層建議做一些設計
1.1 Vs產生的Dockerfile有些格式為整合IDE專有的,需要保留。
1.2 搭配Development與Production階段,建議做一些分層設計。
2. 搭配docker compose可以開啟一次多個站台方便除錯。
3. Visual Studio 2022對Container支援性已蠻方便
.Net支援container的功能,其實Visual Studio 2022已經支援的蠻方便了,除了在IDE運行container性能感覺比較差一些,如果還可以再優化會更好。