---
# System prepended metadata

title: 在 .NET 8 中實現容器化：Docker 應用與經驗分享
tags: [.Net 8, Container, blog]

---

---
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` 處勾選即可

   ![CreateProject2](https://hackmd.io/_uploads/ry6hT5zsyl.png)

#### 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

![image](https://hackmd.io/_uploads/rJE2bozsJg.png)



### 2. 在IDE運行Container站台與中斷debug

基本上前一個步驟完成後，這邊和一般其他專案的debug是一樣的

點選IDE上方的Container(Dockerfile)啟動站台，畫面如下，

![image](https://hackmd.io/_uploads/SyhUNjfskl.png)

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

![image](https://hackmd.io/_uploads/SkJd4jMiJe.png)
          

### 3. 支援一次開啟多個container站台 

要可以一次開啟多個container同時進行debug，需要搭配docker compose專案，透過下面的方法可以建立，並且編寫docker compose來描述多個站台的設定，並且同時開啟。

#### 3.1 首次建立 Docker Compose專案的方法

對專案點選右鍵開啟選單->[Add]->[Container Orchestrator Support]

![DockerCompose1](https://hackmd.io/_uploads/B1DJ6szjkl.png)

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

![DockerCompose2](https://hackmd.io/_uploads/H1FMpsfjJe.png)

#### 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性能感覺比較差一些，如果還可以再優化會更好。


